From b262180857f080af6eb8b3f648f210fd149db2af Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Fri, 26 Aug 2011 21:48:40 +0200 Subject: [PATCH 01/39] Some preliminary work for unified dictionary tag interface support. - toDict() and fromDict() for XiphComments - toDict() for ID3v2 Tags --- taglib/CMakeLists.txt | 2 + taglib/mpeg/id3v2/id3v2dicttools.cpp | 156 +++++++++++++++++++++++++++ taglib/mpeg/id3v2/id3v2dicttools.h | 54 ++++++++++ taglib/mpeg/id3v2/id3v2tag.cpp | 118 +++++++++++++++++++- taglib/mpeg/id3v2/id3v2tag.h | 10 ++ taglib/ogg/xiphcomment.cpp | 41 +++++++ taglib/ogg/xiphcomment.h | 10 ++ taglib/tag.h | 8 ++ tests/data/test.ogg | Bin 0 -> 4408 bytes tests/test_id3v2.cpp | 24 +++++ tests/test_ogg.cpp | 47 ++++++++ 11 files changed, 466 insertions(+), 4 deletions(-) create mode 100644 taglib/mpeg/id3v2/id3v2dicttools.cpp create mode 100644 taglib/mpeg/id3v2/id3v2dicttools.h create mode 100644 tests/data/test.ogg diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index c41c1ea6..ec8a5d81 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -54,6 +54,7 @@ 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 @@ -137,6 +138,7 @@ 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/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp new file mode 100644 index 00000000..87e16ed4 --- /dev/null +++ b/taglib/mpeg/id3v2/id3v2dicttools.cpp @@ -0,0 +1,156 @@ +/*************************************************************************** + copyright : (C) 2011 by Michael Helmling + email : supermihi@web.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ +#include "tdebug.h" +#include "id3v2dicttools.h" +#include "tmap.h" +namespace TagLib { + namespace ID3v2 { + + /*! + * A map of translations frameID <-> tag used by the unified dictionary interface. + */ + static const uint numid3frames = 55; + static const char *id3frames[][2] = { + // Text information frames + { "TALB", "ALBUM"}, + { "TBPM", "BPM" }, + { "TCOM", "COMPOSER" }, + { "TCON", "GENRE" }, + { "TCOP", "COPYRIGHT" }, + { "TDEN", "ENCODINGTIME" }, + { "TDLY", "PLAYLISTDELAY" }, + { "TDOR", "ORIGINALRELEASETIME" }, + { "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", "RELEASETIME" }, + { "TDTG", "TAGGINGTIME" }, + { "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", "PERFORMER" }, + { "TPE3", "CONDUCTOR" }, + { "TPE4", "ARRANGER" }, + { "TPOS", "DISCNUMBER" }, + { "TPRO", "PRODUCEDNOTICE" }, + { "TPUB", "PUBLISHER" }, + { "TRCK", "TRACKNUMBER" }, + { "TRSN", "RADIOSTATION" }, + { "TRSO", "RADIOSTATIONOWNER" }, + { "TSOA", "ALBUMSORT" }, + { "TSOP", "ARTISTSORT" }, + { "TSOT", "TITLESORT" }, + { "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" }, + { "UFID", "UNIQUEIDENTIFIER" }, + }; + + // list of frameIDs that are ignored by the unified dictionary interface + static const uint ignoredFramesSize = 6; + static const char *ignoredFrames[] = { + "TCMP", // illegal 'Part of Compilation' frame set by iTunes (see http://www.id3.org/Compliance_Issues) + "GEOB", // no way to handle a general encapsulated object by the dict interface + "PRIV", // private frames + "APIC", // attached picture -- TODO how could we do this? + "POPM", // popularimeter + "RVA2", // relative volume + }; + + // 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 + }; + + String frameIDToTagName(const ByteVector &id) { + static Map m; + if (m.isEmpty()) + for (size_t i = 0; i < numid3frames; ++i) + m[id3frames[i][0]] = id3frames[i][1]; + + if (m.contains(id)) + return m[id]; + if (deprecationMap().contains(id)) + return m[deprecationMap()[id]]; + debug("unknown frame ID: " + id); + return "UNKNOWNID3TAG"; //TODO: implement this nicer + } + + bool isIgnored(const ByteVector& id) { + List ignoredList; + if (ignoredList.isEmpty()) + for (uint i = 0; i < ignoredFramesSize; ++i) + ignoredList.append(ignoredFrames[i]); + return ignoredList.contains(id); + } + + FrameIDMap deprecationMap() { + static FrameIDMap depMap; + if (depMap.isEmpty()) + for(uint i = 0; i < deprecatedFramesSize; ++i) + depMap[deprecatedFrames[i][0]] = deprecatedFrames[i][1]; + return depMap; + } + + bool isDeprecated(const ByteVector& id) { + return deprecationMap().contains(id); + } + } +} diff --git a/taglib/mpeg/id3v2/id3v2dicttools.h b/taglib/mpeg/id3v2/id3v2dicttools.h new file mode 100644 index 00000000..a6209e7e --- /dev/null +++ b/taglib/mpeg/id3v2/id3v2dicttools.h @@ -0,0 +1,54 @@ +/*************************************************************************** + copyright : (C) 2011 by Michael Helmling + email : supermihi@web.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef ID3V2DICTTOOLS_H_ +#define ID3V2DICTTOOLS_H_ + +#include "tstringlist.h" +#include "taglib_export.h" +#include "tmap.h" + +namespace TagLib { + namespace ID3v2 { + /*! + * This file contains methods used by the unified dictionary interface for ID3v2 tags + * (tag name conversion, handling of un-translatable frameIDs, ...). + */ + typedef Map FrameIDMap; + + String TAGLIB_EXPORT frameIDToTagName(const ByteVector &id); + + bool TAGLIB_EXPORT isIgnored(const ByteVector &); + + FrameIDMap TAGLIB_EXPORT deprecationMap(); + + bool TAGLIB_EXPORT isDeprecated(const ByteVector&); + + + } +} + + +#endif /* ID3V2DICTTOOLS_H_ */ diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 5b4c5c5b..8f746865 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -31,11 +31,15 @@ #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" using namespace TagLib; using namespace ID3v2; @@ -324,9 +328,115 @@ void ID3v2::Tag::removeFrame(Frame *frame, bool del) void ID3v2::Tag::removeFrames(const ByteVector &id) { - FrameList l = d->frameListMap[id]; - for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) - removeFrame(*it, true); + FrameList l = d->frameListMap[id]; + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) + removeFrame(*it, true); +} + +TagDict ID3v2::Tag::toDict() const +{ + TagDict dict; + FrameList::ConstIterator frameIt = frameList().begin(); + for (; frameIt != frameList().end(); ++frameIt) { + ByteVector id = (*frameIt)->frameID(); + + if (isIgnored(id)) { + debug("found ignored id3 frame " + id); + continue; + } + if (isDeprecated(id)) { + debug("found deprecated id3 frame " + id); + continue; + } + if (id[0] == 'T') { + if (id == "TXXX") { + const UserTextIdentificationFrame *uframe + = dynamic_cast< const UserTextIdentificationFrame* >(*frameIt); + String tagName = uframe->description(); + StringList l(uframe->fieldList()); + // this is done because taglib stores the description also as first entry + // in the field list. (why?) + // + if (l.contains(tagName)) + l.erase(l.find(tagName)); + // handle user text frames set by the QuodLibet / exFalso package, + // which sets the description to QuodLibet:: instead of simply + // . + int pos = tagName.find("::"); + tagName = (pos != -1) ? tagName.substr(pos+2) : tagName; + dict[tagName.upper()].append(l); + } + else { + const TextIdentificationFrame* tframe + = dynamic_cast< const TextIdentificationFrame* >(*frameIt); + String tagName = frameIDToTagName(id); + StringList l = tframe->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 lit = l.begin(); lit != l.end(); ++lit) { + bool ok = false; + int test = lit->toInt(&ok); // test if the genre value is an integer + if (ok) { + *lit = ID3v1::genre(test); + } + } + } + else if (tagName == "DATE") { + for (StringList::Iterator lit = l.begin(); lit != l.end(); ++lit) { + // 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 = lit->find("T"); + if (tpos != -1) + (*lit)[tpos] = ' '; + } + } + dict[tagName].append(l); + } + continue; + } + if (id[0] == 'W') { + if (id == "WXXX") { + const UserUrlLinkFrame *uframe = dynamic_cast< const UserUrlLinkFrame* >(*frameIt); + String tagname = uframe->description().upper(); + if (tagname == "") + tagname = "URL"; + dict[tagname].append(uframe->url()); + } + else { + const UrlLinkFrame* uframe = dynamic_cast< const UrlLinkFrame* >(*frameIt); + dict[frameIDToTagName(id)].append(uframe->url()); + } + continue; + } + if (id == "COMM") { + const CommentsFrame *cframe = dynamic_cast< const CommentsFrame* >(*frameIt); + String tagName = cframe->description().upper(); + if (tagName.isEmpty()) + tagName = "COMMENT"; + dict[tagName].append(cframe->text()); + continue; + } + if (id == "USLT") { + const UnsynchronizedLyricsFrame *uframe + = dynamic_cast< const UnsynchronizedLyricsFrame* >(*frameIt); + dict["LYRICS"].append(uframe->text()); + continue; + } + if (id == "UFID") { + const UniqueFileIdentifierFrame *uframe + = dynamic_cast< const UniqueFileIdentifierFrame* >(*frameIt); + String value = uframe->identifier(); + if (!uframe->owner().isEmpty()) + value.append(" [" + uframe->owner() + "]"); + dict["UNIQUEIDENTIFIER"].append(value); + continue; + } + debug("unknown frame ID: " + id); + } + return dict; } ByteVector ID3v2::Tag::render() const diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 4a52854a..26eab2eb 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -260,6 +260,16 @@ namespace TagLib { */ void removeFrames(const ByteVector &id); + /*! + * Implements the unified tag dictionary interface -- export function. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + */ + void fromDict(const TagDict &); + /*! * Render the tag back to binary data, suitable to be written to disk. */ diff --git a/taglib/ogg/xiphcomment.cpp b/taglib/ogg/xiphcomment.cpp index c26391a9..1d083a71 100644 --- a/taglib/ogg/xiphcomment.cpp +++ b/taglib/ogg/xiphcomment.cpp @@ -188,6 +188,47 @@ const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const return d->fieldListMap; } +TagDict Ogg::XiphComment::toDict() const +{ + return d->fieldListMap; +} + +void Ogg::XiphComment::fromDict(const TagDict &tagDict) +{ + // check which keys are to be deleted + StringList toRemove; + FieldListMap::ConstIterator it = d->fieldListMap.begin(); + for(; it != d->fieldListMap.end(); ++it) { + if (!tagDict.contains(it->first)) + toRemove.append(it->first); + } + + StringList::ConstIterator removeIt = toRemove.begin(); + for (; removeIt != toRemove.end(); ++removeIt) + removeField(*removeIt); + + /* now go through keys in tagDict and check that the values match those in the xiph comment */ + TagDict::ConstIterator tagIt = tagDict.begin(); + for (; tagIt != tagDict.end(); ++tagIt) + { + if (!d->fieldListMap.contains(tagIt->first) || !(tagIt->second == d->fieldListMap[tagIt->first])) { + const StringList &sl = tagIt->second; + if(sl.size() == 0) { + // zero size string list -> remove the tag with all values + removeField(tagIt->first); + } + else { + // replace all strings in the list for the tag + StringList::ConstIterator valueIterator = sl.begin(); + addField(tagIt->first, *valueIterator, true); + ++valueIterator; + for(; valueIterator != sl.end(); ++valueIterator) + addField(tagIt->first, *valueIterator, false); + } + } + } +} + String Ogg::XiphComment::vendorID() const { return d->vendorID; diff --git a/taglib/ogg/xiphcomment.h b/taglib/ogg/xiphcomment.h index b105dd6a..9eb329b3 100644 --- a/taglib/ogg/xiphcomment.h +++ b/taglib/ogg/xiphcomment.h @@ -140,6 +140,16 @@ namespace TagLib { */ const FieldListMap &fieldListMap() const; + /*! + * Implements the unified tag dictionary interface -- export function. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + */ + void fromDict(const TagDict &); + /*! * Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the * most common case always returns "Xiph.Org libVorbis I 20020717". diff --git a/taglib/tag.h b/taglib/tag.h index c8f12a85..528d25f8 100644 --- a/taglib/tag.h +++ b/taglib/tag.h @@ -28,9 +28,17 @@ #include "taglib_export.h" #include "tstring.h" +#include "tmap.h" namespace TagLib { + /*! + * This is used for the unified dictionary interface: the tags of a file are + * represented as a dictionary mapping a string (the tag name) to a list of + * strings (the values). + */ + typedef Map TagDict; + //! A simple, generic interface to common audio meta data fields /*! diff --git a/tests/data/test.ogg b/tests/data/test.ogg new file mode 100644 index 0000000000000000000000000000000000000000..220f76f0cef41c7e50736c9134ffb20f0d297961 GIT binary patch literal 4408 zcmeHKeNa$7nQR(UO;ZOjNg)+iXrb2KopnX2ibXyux^=s?r3;A8j=TO*XaCtf?*%`)|8!^E zKRP>m=jP_zd(J)Q-ru?J+;j75Yl~0>S}Mage*XeRE-Smelkp7W)xC{XMiVsYHU63@(SC8+$lcau;)5_0yv3yKq zati%jYJ0i(z=Ttes0pvDnz=TIiY2s(HkP=osT+Z^D`3n>jbZ8sjYd%iTor4zZB~}W zw*DpSKHsKMlwdHnAh4}epYmFdD?k{}%q_4+D;D^+vr@66^_=X~DjPFX+|l}t?80Z( zsgyXUXzHN2zB*{ z9X;Y$EO{^%T|fv^7IHt?$G`Y1{>1_Qa@LB{D1;b@1#-C#e{SbDxh}laC8F!x8zJe~ zAYC))>A46M6tXq@xXb8yH3+e~vK)aDN1)CzZ|`K;4jzv{7KE5!M!u;idy6~gO0oXh z@SYow)pCy^iJlG^VpGPQebIJVWKuNk2Uv!Md9?$I z)xO-J7S}tDj}`Mi)A+?XU-~#uJ-$-57v$!B=ekN`!&K@(%Y;`#jqMx1wZq351!`@7 zi_`n&5KvM5#!m0n0ibd}ZXw^{ubYtKy`b6_p#@liSt0^kKX#T4|^y= z%h4diewEn4H!J@{u%_ng1e-Zk$r-m)-!8~vlC3`V;^ab|-@Sn)I<$|}riKBJKuVUd zjZ5Y}XsYbOgS^H|#Cu(>T1j7TPe;a>^n=TEv*FJ158_^6r=P<%J=cGZEZUPfQ?mQ7 zig~Z~!);}|`-ey^5pa{SG=2iWULBoO<1C~!SJ{d8e2uY^-Y}d3$~6e(|D8X{S^cQ~ zjemtBDvXDkHl2|Q4c9#8(vS9+UvI1UcK;Vp@0%-2JciQm)bQ3JNC@Dthc`lu z@=TW%3%DD5uuX6%YT$06&s*LgzI6)!bV{0kPoAH!v5>2omg^@ID++(Kr|@~hbY?}- z^A-ASdtUbJDIYf9c-wH}+x=fYRx6g#TZ~6&gKG`pN+DdcS)F*EvhIQeDWj<`(($#p zb2iI`=ea~iS8a=@_lWn_k)x6jErKvgges0;v!k!s(c9d)V0YCW@$~k2pSu*ibNa;X z@2kt{1CArqgl}oYw~)Ao1cFA_!luyWkEG+8M(Nm;JiN!rgpB~`%T3{+vE%@R*cVT7 z$0qr@c+pidO<$CDmDF4<(tlskP0Mu&0Bj6$GLRE1N=qB{#?6;>y0=CFz3GBIGMKDcG&S zb6=yjgU6Xj9fA0cs#5P<%h6Ovu+JOTqM4NIiu8G|V9QZRmW|) z(r_rW2!`^xAg#Y@{)*B77DJ1tX`|D{I=#6<;0PMMp-||=@|=7w=&ZPAJ~01Ak1-T_ z|MWUcN2xPcltxo44Bftzq11z>ikCeMYRc`?gX{wRHQ;*;i0Vnl0QERNB-$NaZ*@r8 zPfpsFuT*vmpNp`F5jw*Qa*Hm>)(ZVt@+fZOs@IAFQ)EcsCrKjFR;-nXj=9NQgdQV_ zK6k8IB05AObp1di{MriYE@+WpC9=piQ44jMpHs}2X%Vr_YbLsF`9Cc zi%S%yCElfGP){TxV5Pa3RvhqimtIVLem6ytYS=9}ik1`kA;YD{Sl!?}Ezt(k0&dtc z5s25uh-~pBK?Jy3mJpaQR;-Qbw#CEYq_77CS^~Vw&w}Kbr%FxIuc|y2^VBops8Rzt zPmh}<(lo6p0iK_?b#tPrpv=+R$R#wc8X2QBj2~7AHcH%&7I~%uV!_32TaAZh0D61UWnHIV0+Z=7JcEQ^ZhKwW`;l z;*#zDitWm`8S56lJ`pKo3Rx;?T$hE(_ce?p>}@`~;-v{fVitg2gjS%tu)ytz!picture().size()); } + void testDictInterface() + { + ScopedFileCopy copy("rare_frames", ".mp3"); + string newname = copy.fileName(); + MPEG::File f(newname.c_str()); + TagDict dict = f.ID3v2Tag(false)->toDict(); + CPPUNIT_ASSERT_EQUAL(uint(7), dict.size()); + CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]); + CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]); + CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION2"][0]); + CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION2"][1]); + + CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"][0]); + CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"][0]); + + CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["USERURL"][0]); + CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"][0]); + + CPPUNIT_ASSERT_EQUAL(String("12345678 [supermihi@web.de]"), dict["UNIQUEIDENTIFIER"][0]); + + CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"][0]); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index 9e845096..de25d3ed 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -17,6 +17,8 @@ class TestOGG : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestOGG); CPPUNIT_TEST(testSimple); CPPUNIT_TEST(testSplitPackets); + CPPUNIT_TEST(testDictInterface1); + CPPUNIT_TEST(testDictInterface2); CPPUNIT_TEST_SUITE_END(); public: @@ -51,6 +53,51 @@ public: delete f; } + void testDictInterface1() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + Vorbis::File *f = new Vorbis::File(newname.c_str()); + + CPPUNIT_ASSERT_EQUAL(uint(0), f->tag()->toDict().size()); + + TagDict newTags; + StringList values("value 1"); + values.append("value 2"); + newTags["ARTIST"] = values; + f->tag()->fromDict(newTags); + + TagDict map = f->tag()->toDict(); + CPPUNIT_ASSERT_EQUAL(uint(1), map.size()); + CPPUNIT_ASSERT_EQUAL(uint(2), map["ARTIST"].size()); + CPPUNIT_ASSERT_EQUAL(String("value 1"), map["ARTIST"][0]); + delete f; + + } + + void testDictInterface2() + { + ScopedFileCopy copy("test", ".ogg"); + string newname = copy.fileName(); + + Vorbis::File *f = new Vorbis::File(newname.c_str()); + TagDict tags = f->tag()->toDict(); + + CPPUNIT_ASSERT_EQUAL(uint(2), tags["UNUSUALTAG"].size()); + CPPUNIT_ASSERT_EQUAL(String("usual value"), tags["UNUSUALTAG"][0]); + CPPUNIT_ASSERT_EQUAL(String("another value"), tags["UNUSUALTAG"][1]); + CPPUNIT_ASSERT_EQUAL(String(L"öäüoΣø"), tags["UNICODETAG"][0]); + + tags["UNICODETAG"][0] = L"νεω ναλυε"; + tags.erase("UNUSUALTAG"); + f->tag()->fromDict(tags); + CPPUNIT_ASSERT_EQUAL(String(L"νεω ναλυε"), f->tag()->toDict()["UNICODETAG"][0]); + CPPUNIT_ASSERT_EQUAL(false, f->tag()->toDict().contains("UNUSUALTAG")); + + delete f; + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG); From 58db919e435f4656a4ef67c5fd2582668b6f6863 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 27 Aug 2011 01:18:21 +0200 Subject: [PATCH 02/39] More support for the unified dictionary interface. Addded fromDict() function to ID3v2Tag. Added fromDict() and toDict() functions to the TagUnion class (uses the first non-empty tag). Added fromDict() and toDict() functions for the generic Tag class, only handling common tags without duplicates. Addded preliminary mp3 test case. Python3 bindings now available on my github site. --- taglib/mpeg/id3v2/id3v2dicttools.cpp | 16 ++- taglib/mpeg/id3v2/id3v2dicttools.h | 4 +- taglib/mpeg/id3v2/id3v2tag.cpp | 195 +++++++++++++++++++++++++-- taglib/mpeg/id3v2/id3v2tag.h | 4 +- taglib/ogg/xiphcomment.h | 4 +- taglib/tag.cpp | 71 +++++++++- taglib/tag.h | 16 +++ taglib/tagunion.cpp | 16 +++ taglib/tagunion.h | 3 + tests/data/rare_frames.mp3 | Bin 0 -> 8320 bytes tests/test_id3v2.cpp | 4 +- 11 files changed, 312 insertions(+), 21 deletions(-) create mode 100644 tests/data/rare_frames.mp3 diff --git a/taglib/mpeg/id3v2/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp index 87e16ed4..903c9372 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.cpp +++ b/taglib/mpeg/id3v2/id3v2dicttools.cpp @@ -31,7 +31,7 @@ namespace TagLib { /*! * A map of translations frameID <-> tag used by the unified dictionary interface. */ - static const uint numid3frames = 55; + static const uint numid3frames = 54; static const char *id3frames[][2] = { // Text information frames { "TALB", "ALBUM"}, @@ -96,11 +96,10 @@ namespace TagLib { // Other frames { "COMM", "COMMENT" }, { "USLT", "LYRICS" }, - { "UFID", "UNIQUEIDENTIFIER" }, }; // list of frameIDs that are ignored by the unified dictionary interface - static const uint ignoredFramesSize = 6; + static const uint ignoredFramesSize = 7; static const char *ignoredFrames[] = { "TCMP", // illegal 'Part of Compilation' frame set by iTunes (see http://www.id3.org/Compliance_Issues) "GEOB", // no way to handle a general encapsulated object by the dict interface @@ -108,6 +107,7 @@ namespace TagLib { "APIC", // attached picture -- TODO how could we do this? "POPM", // popularimeter "RVA2", // relative volume + "UFID", // unique file identifier }; // list of deprecated frames and their successors @@ -133,6 +133,16 @@ namespace TagLib { return "UNKNOWNID3TAG"; //TODO: implement this nicer } + ByteVector tagNameToFrameID(const String &s) { + static Map m; + if (m.isEmpty()) + for (size_t i = 0; i < numid3frames; ++i) + m[id3frames[i][1]] = id3frames[i][0]; + if (m.contains(s.upper())) + return m[s]; + return "TXXX"; + } + bool isIgnored(const ByteVector& id) { List ignoredList; if (ignoredList.isEmpty()) diff --git a/taglib/mpeg/id3v2/id3v2dicttools.h b/taglib/mpeg/id3v2/id3v2dicttools.h index a6209e7e..3e7a329f 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.h +++ b/taglib/mpeg/id3v2/id3v2dicttools.h @@ -38,7 +38,9 @@ namespace TagLib { */ typedef Map FrameIDMap; - String TAGLIB_EXPORT frameIDToTagName(const ByteVector &id); + ByteVector TAGLIB_EXPORT tagNameToFrameID(const String &); + + String TAGLIB_EXPORT frameIDToTagName(const ByteVector &); bool TAGLIB_EXPORT isIgnored(const ByteVector &); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 8f746865..83406cdb 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -425,20 +425,197 @@ TagDict ID3v2::Tag::toDict() const dict["LYRICS"].append(uframe->text()); continue; } - if (id == "UFID") { - const UniqueFileIdentifierFrame *uframe - = dynamic_cast< const UniqueFileIdentifierFrame* >(*frameIt); - String value = uframe->identifier(); - if (!uframe->owner().isEmpty()) - value.append(" [" + uframe->owner() + "]"); - dict["UNIQUEIDENTIFIER"].append(value); - continue; - } debug("unknown frame ID: " + id); } return dict; } +void ID3v2::Tag::fromDict(const TagDict &dict) +{ + FrameList toRemove; + // first record what frames to remove; we do not remove in-place + // because that would invalidate FrameListMap iterators. + // + for (FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) { + if (it->second.size() == 0) // ignore empty map entries (does this ever happen?) + continue; + if (isDeprecated(it->first))// automatically remove deprecated frames + toRemove.append(it->second); + else if (it->first == "TXXX") { // handle user text frames specially + for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { + UserTextIdentificationFrame* frame + = dynamic_cast< UserTextIdentificationFrame* >(*fit); + String tagName = frame->description(); + int pos = tagName.find("::"); + tagName = (pos == -1) ? tagName : tagName.substr(pos+2); + if (!dict.contains(tagName.upper())) + toRemove.append(frame); + } + } + else if (it->first == "WXXX") { // handle user URL frames specially + for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { + UserUrlLinkFrame* frame = dynamic_cast(*fit); + String tagName = frame->description().upper(); + if (!(tagName == "URL") || !dict.contains("URL") || dict["URL"].size() > 1) + toRemove.append(frame); + } + } + else if (it->first == "COMM") { + for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { + CommentsFrame* frame = dynamic_cast< CommentsFrame* >(*fit); + String tagName = frame->description().upper(); + // policy: use comment frame only with empty description and only if a comment tag + // is present in the dictionary and only if there's no more than one comment + // (COMM is not specified for multiple values) + if ( !(tagName == "") || !dict.contains("COMMENT") || dict["COMMENT"].size() > 1) + toRemove.append(frame); + } + } + else if (it->first == "USLT") { + for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { + UnsynchronizedLyricsFrame *frame + = dynamic_cast< UnsynchronizedLyricsFrame* >(*fit); + String tagName = frame->description().upper(); + if ( !(tagName == "") || !dict.contains("LYRICS") || dict["LYRICS"].size() > 1) + toRemove.append(frame); + } + } + else if (it->first[0] == 'T') { // a normal text frame + if (!dict.contains(frameIDToTagName(it->first))) + toRemove.append(it->second); + + } else + debug("file contains unknown tag" + it->first + ", not touching it..."); + } + + // now remove the frames that have been determined above + for (FrameList::ConstIterator it = toRemove.begin(); it != toRemove.end(); it++) + removeFrame(*it); + + // now sync in the "forward direction" + for (TagDict::ConstIterator it = dict.begin(); it != dict.end(); ++it) { + const String &tagName = it->first; + ByteVector id = tagNameToFrameID(tagName); + if (id[0] == 'T' && id != "TXXX") { + // the easiest case: a normal text frame + StringList values = it->second; + const FrameList &framelist = frameList(id); + if (tagName == "DATE") { + // Handle ISO8601 date format (see above) + for (StringList::Iterator lit = values.begin(); lit != values.end(); ++lit) { + if (lit->length() > 10 && (*lit)[10] == ' ') + (*lit)[10] = 'T'; + } + } + if (framelist.size() > 0) { // there exists already a frame for this tag + const TextIdentificationFrame *frame = dynamic_cast(framelist[0]); + if (values == frame->fieldList()) + continue; // equal tag values -> everything ok + } + // if there was no frame for this tag, or there was one but the values aren't equal, + // we start from scratch and create a new one + // + removeFrames(id); + TextIdentificationFrame *frame = new TextIdentificationFrame(id); + frame->setText(values); + addFrame(frame); + } + else if (id == "TXXX" || + ((id == "WXXX" || id == "COMM" || id == "USLT") && it->second.size() > 1)) { + // In all those cases, we store the tag as TXXX frame. + // First we search for existing TXXX frames with correct description + FrameList existingFrames; + FrameList l = frameList("TXXX"); + + for (FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) { + String desc= dynamic_cast< UserTextIdentificationFrame* >(*fit)->description(); + int pos = desc.find("::"); + String tagName = (pos == -1) ? desc.upper() : desc.substr(pos+2).upper(); + if (tagName == it->first) + existingFrames.append(*fit); + } + + bool needsInsert = false; + if (existingFrames.size() > 1) { //several tags with same key, remove all and reinsert + for (FrameList::ConstIterator it = existingFrames.begin(); it != existingFrames.end(); ++it) + removeFrame(*it); + needsInsert = true; + } + else if (existingFrames.isEmpty()) // no frame -> needs insert + needsInsert = true; + else { + if (!(dynamic_cast< UserTextIdentificationFrame*>(existingFrames[0])->fieldList() == it->second)) { + needsInsert = true; + removeFrame(existingFrames[0]); + } + } + if (needsInsert) { // create and insert new frame + UserTextIdentificationFrame* frame = new UserTextIdentificationFrame(); + frame->setDescription(it->first); + frame->setText(it->second); + addFrame(frame); + } + } + else if (id == "WXXX") { + // we know that it->second.size()==1, since the other cases are handled above + bool needsInsert = true; + FrameList existingFrames = frameList(id); + if (existingFrames.size() > 1 ) // do not allow several WXXX frames + removeFrames(id); + else if (existingFrames.size() == 1) { + needsInsert = !(dynamic_cast< UserUrlLinkFrame* >(existingFrames[0])->url() == it->second[0]); + if (needsInsert) + removeFrames(id); + } + if (needsInsert) { + UserUrlLinkFrame* frame = new ID3v2::UserUrlLinkFrame(); + frame->setDescription(it->first); + frame->setUrl(it->second[0]); + addFrame(frame); + } + } + else if (id == "COMM") { + FrameList existingFrames = frameList(id); + bool needsInsert = true; + if (existingFrames.size() > 1) // do not allow several COMM frames + removeFrames(id); + else if (existingFrames.size() == 1) { + needsInsert = !(dynamic_cast< CommentsFrame* >(existingFrames[0])->text() == it->second[0]); + if (needsInsert) + removeFrames(id); + } + + if (needsInsert) { + CommentsFrame* frame = new CommentsFrame(); + frame->setDescription(""); // most software players use empty description COMM frames for comments + frame->setText(it->second[0]); + addFrame(frame); + } + } + else if (id == "USLT") { + FrameList existingFrames = frameList(id); + bool needsInsert = true; + if (existingFrames.size() > 1) // do not allow several USLT frames + removeFrames(id); + else if (existingFrames.size() == 1) { + needsInsert = !(dynamic_cast< UnsynchronizedLyricsFrame* >(existingFrames[0])->text() == it->second[0]); + if (needsInsert) + removeFrames(id); + } + + if (needsInsert) { + UnsynchronizedLyricsFrame* frame = new UnsynchronizedLyricsFrame(); + frame->setDescription(""); + frame->setText(it->second[0]); + addFrame(frame); + } + } + else + debug("ERROR: Don't know how to translate tag " + it->first + " to ID3v2!"); + + } +} + ByteVector ID3v2::Tag::render() const { return render(4); diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 26eab2eb..715daf04 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -263,12 +263,12 @@ namespace TagLib { /*! * Implements the unified tag dictionary interface -- export function. */ - TagDict toDict() const; + virtual TagDict toDict() const; /*! * Implements the unified tag dictionary interface -- import function. */ - void fromDict(const TagDict &); + virtual void fromDict(const TagDict &); /*! * Render the tag back to binary data, suitable to be written to disk. diff --git a/taglib/ogg/xiphcomment.h b/taglib/ogg/xiphcomment.h index 9eb329b3..988f616d 100644 --- a/taglib/ogg/xiphcomment.h +++ b/taglib/ogg/xiphcomment.h @@ -143,12 +143,12 @@ namespace TagLib { /*! * Implements the unified tag dictionary interface -- export function. */ - TagDict toDict() const; + virtual TagDict toDict() const; /*! * Implements the unified tag dictionary interface -- import function. */ - void fromDict(const TagDict &); + virtual void fromDict(const TagDict &); /*! * Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the diff --git a/taglib/tag.cpp b/taglib/tag.cpp index 8be33c80..9e0ea258 100644 --- a/taglib/tag.cpp +++ b/taglib/tag.cpp @@ -24,7 +24,7 @@ ***************************************************************************/ #include "tag.h" - +#include "tstringlist.h" using namespace TagLib; class Tag::TagPrivate @@ -53,6 +53,75 @@ bool Tag::isEmpty() const track() == 0); } +TagDict Tag::toDict() const +{ + TagDict dict; + if (!(title() == String::null)) + dict["TITLE"].append(title()); + if (!(artist() == String::null)) + dict["ARTIST"].append(artist()); + if (!(album() == String::null)) + dict["ALBUM"].append(album()); + if (!(comment() == String::null)) + dict["COMMENT"].append(comment()); + if (!(genre() == String::null)) + dict["GENRE"].append(genre()); + if (!(year() == 0)) + dict["DATE"].append(String::number(year())); + if (!(track() == 0)) + dict["TRACKNUMBER"].append(String::number(track())); + return dict; +} + +void Tag::fromDict(const TagDict &dict) +{ + if (dict.contains("TITLE") and dict["TITLE"].size() >= 1) + setTitle(dict["TITLE"].front()); + else + setTitle(String::null); + + if (dict.contains("ARTIST") and dict["ARTIST"].size() >= 1) + setArtist(dict["ARTIST"].front()); + else + setArtist(String::null); + + if (dict.contains("ALBUM") and dict["ALBUM"].size() >= 1) + setAlbum(dict["ALBUM"].front()); + else + setAlbum(String::null); + + if (dict.contains("COMMENT") and dict["COMMENT"].size() >= 1) + setComment(dict["COMMENT"].front()); + else + setComment(String::null); + + if (dict.contains("GENRE") and dict["GENRE"].size() >=1) + setGenre(dict["GENRE"].front()); + else + setGenre(String::null); + + if (dict.contains("DATE") and dict["DATE"].size() >= 1) { + bool ok; + int date = dict["DATE"].front().toInt(&ok); + if (ok) + setYear(date); + else + setYear(0); + } + else + setYear(0); + + if (dict.contains("TRACKNUMBER") and dict["TRACKNUMBER"].size() >= 1) { + bool ok; + int track = dict["TRACKNUMBER"].front().toInt(&ok); + if (ok) + setTrack(track); + else + setTrack(0); + } + else + setYear(0); +} void Tag::duplicate(const Tag *source, Tag *target, bool overwrite) // static { if(overwrite) { diff --git a/taglib/tag.h b/taglib/tag.h index 528d25f8..45caf083 100644 --- a/taglib/tag.h +++ b/taglib/tag.h @@ -58,6 +58,22 @@ namespace TagLib { */ virtual ~Tag(); + /*! + * Unified tag dictionary interface -- export function. Converts the tags + * of the specific metadata format into a "human-readable" map of strings + * to lists of strings, being as precise as possible. + */ + virtual TagDict toDict() const; + + /*! + * Unified tag dictionary interface -- import function. Converts a map + * of strings to stringslists into the specific metadata format. Note that + * not all formats can store arbitrary tags and values, so data might + * be lost by this operation. Especially the default implementation handles + * only single values of the default tags specified in this class. + */ + virtual void fromDict(const TagDict &); + /*! * Returns the track name; if no track name is present in the tag * String::null will be returned. diff --git a/taglib/tagunion.cpp b/taglib/tagunion.cpp index 4a9978d0..2ecdd6d9 100644 --- a/taglib/tagunion.cpp +++ b/taglib/tagunion.cpp @@ -24,6 +24,7 @@ ***************************************************************************/ #include "tagunion.h" +#include "tstringlist.h" using namespace TagLib; @@ -170,6 +171,21 @@ 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 e94d523a..20771fe8 100644 --- a/taglib/tagunion.h +++ b/taglib/tagunion.h @@ -73,6 +73,9 @@ 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/tests/data/rare_frames.mp3 b/tests/data/rare_frames.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e485337f9e489dea7fecc899c7418cadc923c4b4 GIT binary patch literal 8320 zcmeHsXHXPDxArXT5|APY-al7OIyB1_H)2uo1OIZ0kJh(w8k z3K9fCkSvlUh5u%cIjMg& zRU|Yu3b=>TMLRnCD=7RAC{q6xMa@8;fI$4$37Ew{yAH)ejGfSbR+kAk>3QtU6V zSb(4BzifmEHvhd+{2|)kP2Abb$N!Ny>OcEo21lq9%vk{7et?g&pBLHW!mLB$l7yT)4P^C$Xd_aoLa1#4O%Z+pW_a@ zXxgQ5KU?~6v29LmPOZN?ylRe%_e-owg^C($B^osdXg522I!FoaWxJMpIR`R(V}|7V z9R?={-3BE3hla@p-3BKIB!=XM5mNy6x>IoKv)Qymv?Ahp;ZA~M1fNdLX zf7PDb;nOzK6x?Eo+i$wkDuUZ@8zFe=(&pAG)aKk`+r)%B?&W;j(|0=T^B4W^@fZ1D zsoP)Nfy{x-zdQui$)P-gMkpQj8J_JEXqWwW(fi+0hd;lfJ_h=Sw>lUAAOiq$AAoS1 z0gxD>kP`s-dH?`ShX62?VYI3wAgL#Y0N{lINV4B(VF04DeZ$EbJSUil5|2MyKA$Po z-%Izd0y#KHfD`ioS%Kj0d3@jsQR}O}JRHu`0l#O3-iS# z(UX*N?2iQu6h;i(!*}EHmz^SG!`;eZ0zE9R>aW3+d%6PZp(kNR#` zQ}bUQZ>&ECcnS=Z5y)5_Sd`cSfXEZXj3HzdFsNl806g#FFH3VrWj>{-|6{577eUQ( ztzzkv^2RaFs0Pu2JjN)I{PkGuI(t|NU33X|G(UZK30)*VKiL2!u+AUP&rqxRm*-vK zJpjOhv{W1bGJ1?4nFs(P(j^wpC&We#Q&dFY5rmWUT!ZtFM$v)70QP<^hk*jacgWeE zysAacE!xiu+lp@$H+;r9GfjA#s6|(Yg{yT}Tq-ci2@crRTj^3w#9DuOGPpbd;07>~ zrwovhU+=DPV-{k`v(485@oIO004geGj42o z0&9c~2ovf;Flp_IN->rXS)K25-LF$>!)Y~#sU$v+9Tex_5owgk0~7pn5 zcSdzc6%o@PM*Cq}MLv4kanu^_>;!6(NkB)i9fGH)#yPm;O}>)b*!8`lgKE% zT*>b-fF!j?Yo7+O6jV4zv98k=oo=1{qXFM-Ai98OpHa z_nKfzx_0}obfv0lx-8O>%%RK6x&kDQoFs8QMt$aPV`owgMs;~M3dAHyp}`joawU8Y zrVB%Dib?BE3(2;!rn-JykNZ%iX;LO8V=sd8@-Dw6I>(ciJW>L)KRtc0@NmeY8+IrU z3Yb%Je%943#)OjRYY(mh3=Lg~tp$g3eTNtkk5h% z`O~gc=)l*Xuvl$CBqoU3Zns099Cy{SV)N8zw=3y6mG4iSaI6Ljn$w9E{-uSV?%VF) zWFA@H!J$Uj9vi-+jPPJ&4gMBG%D4k9k3AU2Qfmhq%Oy&okgk6Y!(rC$<5;k~{(X@}kD$ zJ&jso;(6S-!bH7+uxH`-{Cn**8DbWHoKGI)oW@_UHrLXm)gjs#CZJtZ;R1OAG$N6K zk=r`ruLUbbIRxR0T;U1+Q#Cyu-;Se3yOBZQIo(D>^GW(M8cBcoo-)+qQ-jEg)su>_ zCc7ztR?~)&ShLy1G&_|KWA?%*2jS0d!nmYsDP$THY4tIbuNz7Vw?eDliZ$$gDthny zoP63<^>~|hE?A1S#-S1|NOxQqIkbqz>9ca@mTKOOuBo4xGMrs~bH|)b)lQTWOObmh2o3x$^y4oc6 zM*SHvtNKYPr+0PM3yG7)L5I_MSBLmrS(>5qvV-aBx-+OGMgSy#|iQ`?u`e}blzSYmrm? z#b8aB^GzXIITBE&O3R0wn5WB&wHui;=})cn|1ii5%RzhU4XUb9oA9Z~h3j+o`O1rD zP?2h@2hKca%qJ<3i-K72s(bgqFJ}VYeIIMKigb*HMM}n=#WeR!%3iJS*lD&?i@ERh z!;Ae!;^@q1){sma2+S>CbAHLe*42~%y?1aRMa8>{=KQQ$ZHZ=}vd${3I6kVq2bCMn zGrOc#);6Ma57fxqK@QxRnwKV$B#$^S)sBxHUz&BWwLwy4A-cs%zhgP(LHf}2&fGA7Qsr?U0feZRaDB2DOzQ2tKO8F>IJ1& z%d#P$YyDrlsTF80`pD$K?a7QHx~NREa{IYaqHOyz67yohx>biNAE(Dac+AXAL^u?m zA%e1z;q4}i;wDwKb6cX6gU*@~0s<>9owur{5Z{fM+0Q+0LNx${MdP7WSg_s^N>QkQqCerVXBM-$oK(=%?OEd*-U%$`DDEw z)I|RA>7kaU>dfct@KzU=43O~jD@z{eH2__wNI|6Bp(~j^boa#no3!nk@1~1*Iz`^3 zt((ERerRUQR(!MvR@gOe>#JE7QS^#^UGLn94r|ntx{!~5>ri+zpQ{tu6t^Pw+m$!-4X4_eR+*){7 z>|@+XZ|tAz8vXg&Z?2mJ`{Y_Q*rx|*+;#~uaTg{TyQZ;GlZPwv@`nbH$2gQ4XiQ;^ zg!6yyPeuIrZUF;FQnnNSB6{fM4tXWZ=0$$Y_5x}>YPt>HQ4=MR`#a+9EmL5?7uugp zXi$oAXi6nyRO@r)pGGn-0fk#R=#$!gNJ3snd5rOmpPM}IhMI_YBW!r0g`}ksJzcbQ zvxOCrs8^X3b*HFiWyO_DwU`|rP0ye^(odu~lHO#h-+=7Arpv156BY3H;F4$g3g@^B zTp~+m6nF-cRNo(YNv*D#&H74SX-y;bc6Dr7M)h88;kBofO1W*XICb#x_0A2=jJ_t^ zs#NrI#1^ZS*iCx9Vee)w-kP#~mGbnNBBSl!>C&|VTFLe^e`M{d8c;Ry)kBca-l@1Z z>0jQs^wgc&R$z#r2IkQtJGJ}SUQT_GZB8^SekL7!!Fsd#eff0(e{ow+_kQYDSF0m; zp`Z0GE5;w2WM(+G<@h~B5P7?YY89cU0c&4`0nQOl{Y*nixQPVnx?fn z%y|~{Rc!B4>)@2P$8kTi)X9Z^outa}G&x#@h!nq{anX<^Zmc|dVf;M@oX>+B0Mrc^W!EN$};XUiM-=iMIkOHLl z4c>d-Qw9cN&lSct0IVR)%q{^C@+%S+uERhKO(pWL6dux%)2d}UWD1m7YW1k}@lo0i zUxDUL2AXbMnL1s2tLMM5og$Kw??S=rsK)GHLM!3Zcqom5TGbW4fsD3iJ&w?oXxuzu zRzjNXPx6jyw9;)66YB*cC!^si9pl`ifjZ&TdLy~;8&Y3yQ6JS)8LzQ;#_e7)Zh9y` zR>U-eJWV^YH{3B2JtcQm5IfKKkT^0E?phctmpB&vE?0~D0hRuP#oxhSW@MRiA=P9; z%4?OM+51cq1Lm42+mCmxcRwp-%*h=|4-Xt9VWmrJRHj>XxMr`5UG^`&5W>V@3}d}+ z&inR&vXe7nwy{u7hF#N9LnB)3=Lacrh78ScM{N=|2{5-Kv1d7K(sUt+nt5s&@-aq@ zcmC`)MpNjTvJsp3S8BMbUUN81>y9tMl&i1wQuI z2n*|&53MdojKbK_uX-|!`R%t{S)m1X8H-PgGq_+TmiH-Pi@D`#gDz`_S!2kvwaSM- z$w-@~II3qNmc%abVXwl&*1tGx6NB>Kcc<28^b8U^#v2Y6>Q8S;@E75)F8WV5JiHRA zDC&HPh9zn6R4wqk75w;+?HJFzV|E#ivJhO_oYz16X}}yVs=s~MXsBLS8{23>AAQl7T~q^)SX^<5GS~0{8cjw7W^AFaqDR5*2NFJFaGV%?{{#c zGQ!eo$a(x&o29o6{!2`K8H79CYZdB2)6R=1?GfZ@4`Tp^nZCL!EPkIML%x#v$Hvrv z0f2xtKbk=G6_U_%ok;#c1{p4+D(Cnu)@$zQ=n`Hcn~~!fn9S*J15WZ~k7xtj-uR>x z+>4C%OJcnijj{BO>A5d$eT!+5M3hle&6qiZb$_`^j>D&*&UO#>%>zreljK6jzWCVd zRgK6id!9B&v@zqt`dxRvXhglYIov*bxJEt9;Avygd=gZolC0MfM*4%*<-+?RE8*38 z27gIAb%$8{s!r6oiAX8@y3b3#goV%Z*0FHQxgT0gFYO?C9V73N!w$2?4er-Xz#^%6qBtVYQBm*ubN+4?3nVuH{ZL3D6`r=Iwm?*6Az6ykBdQk`RUz_ghc$-zN;) zB-J)n8pHUA4@uQVQm5Ev-*Y)9r?~l~qs$!1VSEUq45Oki=&;RlqTb*SB}YC+PGrpY zXjeCQ`K?@{#$pg`I+`H3?L=m}>NSLSGl+Kdvqb(AWh!4zYhmPbDZ&SsA#47qzawt+ z4mAdRs%JV4g|~#eQ7j^(`b{GdR#kNh6^6x?GEw}aRB?vY+*3wZhhG*JxHM{SXhUE5 zXiYMVc}T=kHm5-AG_yv{GR0aaEVJrR`-P=BFRK=LXRUpWi;BWWCphG7f65JW{*gdK z%er-_8$CC)tW0}X-xcvy!_uQX7=&R7oF&q34-%Vpr<>tPB=BriQnybH{sp*aGRU$tZ3!X)+Mu%N(@hoxIp2u1GwBpspD+u61H zn!IlS_ab4b2zO{|!_)?Ud<5Any8YqaXLgmkFNHcUBE{`Gc5qfAotL<7seA~-0`6WD z4NHKr#y9=`r~*&#%Cc69JQB?E%A{1_Z`z$qreR)4iwS>k(q3=4*eAa~je)*JE%zJ~ zTg)ZJL*Me4RbjOomCG^rgFyQ}MqoR{c5)#vazHyD?6w&wSe~vCMyq97xPp9GVC7tF zEtDAxv(U+l)28i*hlDRBNs{+kJ#9Wu>O5Thb9n>E3;y-os#+sf@8nAiixP8Cg*q00 z{syI@MhZY^bln##^K z#iy{DJLoo|tp+9>wSwHYVy`#eQ){&zT9 zSmt$6eO$)GXZ%iQK6%>y4oP+XY|)-*WCU zTSRt~&bPjoFbvriUGIQ<2j#(mbXar0JbPg}>)B5s+lurkyLe(5-Jye*WuI8`)P3e# zdCK*yOo~tKix#G$w^0F(jBXTkq+?g;k{4Wg!UAt?WD05VP;kMi8|^ZmbGJ|!=+?i- zAwFe184lUmX<)y6@|rC~c+le%t1!vO!jHcU{iX<_=Hhy1SlMTb%izfirjy`^Twv^T zD*-zAfFebwQrcBsOE&oCR4G?AFAZ5gh#9VEcJ*d&9z{?jIqiXC9TNPAy;Df%)+gN@ zdMIl2qi5e@f`Pje$-g`sGQ{eTCVo6 z)Pahyl&Yw>$P*XY=1A%!&fkFyybndINeY=f$%T|rck+HR-yCIndl3%*c9B4S_ ztRgT7$cSexQ_*&H^iURzw$zs+Bk|C~6nMk3%BY^<>>`u)!E?gBs_m@mUB2qE7}oNd zffWnz_TBQjHW@YbTHTn{?2oZh3n9_#@~)O%K^0i&528Vtb5}3jrCW)OlV_J4(bsty zGDTmvy!fK`iP9davW2@8AgY-3!rW8tNWUUHqf-2zNbd{;hpmP z{Y-N{a-Gc3aepITJI2rvh7Q(3S5*%|(=4Y@kquE@xX-RZj+Yq4{X-IFPtCAs1%3L5 zrQgHOvUyosR}wr5JYsf|?xoW=)tZp(Wx+t1?E=g76<1Bf{L6#Z4d%3B`e{TGu!CE9 z3wGmLGMs1)Qb~Vu)z#=2{|>o4Y^>6P{DbFKB6_bM@AS)53kw|27*UmbMi~#yTTT;2 zS;q#09VEPh3lG^Vq4QU+C3R^X)8~1nXh?W?LKey0q_0wq#uRTq6Q(y+(Ios$`2Q%J N691C_K>+}&{{TD^OUM8K literal 0 HcmV?d00001 diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 3035ebd8..067bcd22 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -554,7 +554,7 @@ public: string newname = copy.fileName(); MPEG::File f(newname.c_str()); TagDict dict = f.ID3v2Tag(false)->toDict(); - CPPUNIT_ASSERT_EQUAL(uint(7), dict.size()); + CPPUNIT_ASSERT_EQUAL(uint(6), dict.size()); CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]); CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]); CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION2"][0]); @@ -566,8 +566,6 @@ public: CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["USERURL"][0]); CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"][0]); - CPPUNIT_ASSERT_EQUAL(String("12345678 [supermihi@web.de]"), dict["UNIQUEIDENTIFIER"][0]); - CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"][0]); } From fa8159a9d0a0f4d53fe11310ad9f9b35d425f960 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 27 Aug 2011 22:30:20 +0200 Subject: [PATCH 03/39] Added toDict and fromDict methods for APE tags. --- taglib/ape/apetag.cpp | 64 ++++++++++++++++++++++++++++++++++++ taglib/ape/apetag.h | 19 +++++++++++ taglib/mpeg/id3v2/id3v2tag.h | 3 ++ taglib/ogg/xiphcomment.h | 5 +++ 4 files changed, 91 insertions(+) diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 082fd038..1f3f84a2 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -174,6 +174,70 @@ void APE::Tag::setTrack(uint i) addValue("TRACK", String::number(i), true); } +TagDict APE::Tag::toDict() const +{ + TagDict dict; + ItemListMap::ConstIterator it = itemListMap().begin(); + for (; it != itemListMap().end(); ++it) { + String tagName = it->first.upper(); + // These two tags need to be handled specially; in APE tags the track number is usually + // named TRACK instead of TRACKNUMBER, the date tag is YEAR instead of DATE + // + if (tagName == "TRACK") + tagName = "TRACKNUMBER"; + else if (tagName == "YEAR") + tagName = "DATE"; + if (it->second.type() == Item::Text) + dict[tagName].append(it->second.toStringList()); + } + return dict; +} + +void APE::Tag::fromDict(const TagDict &orig_dict) +{ + TagDict dict(orig_dict); // make a local copy that can be modified + + if (dict.contains("TRACKNUMBER")) { + dict.insert("TRACK", dict["TRACKNUMBER"]); + dict.erase("TRACKNUMBER"); + } + if (dict.contains("DATE")) { + dict.insert("YEAR", dict["DATE"]); + dict.erase("DATE"); + } + + // first check if tags need to be removed completely + StringList toRemove; + ItemListMap::ConstIterator remIt = itemListMap().begin(); + for (; remIt != itemListMap().end(); ++remIt) { + if (remIt->second.type() != APE::Item::Text) + // ignore binary and locator APE items + continue; + if (!dict.contains(remIt->first.upper())) + toRemove.append(remIt->first); + } + + for (StringList::Iterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++) + removeItem(*removeIt); + + // now sync in the "forward direction + TagDict::ConstIterator it = dict.begin(); + for (; it != dict.end(); ++it) { + const String &tagName = it->first; + if (!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) { + if (it->second.size() == 0) + removeItem(tagName); + else { + StringList::ConstIterator valueIt = it->second.begin(); + addValue(tagName, *valueIt, true); + ++valueIt; + for(; valueIt != it->second.end(); ++valueIt) + addValue(tagName, *valueIt, false); + } + } + } +} + APE::Footer *APE::Tag::footer() const { return &d->footer; diff --git a/taglib/ape/apetag.h b/taglib/ape/apetag.h index 13efd5e0..b48d9291 100644 --- a/taglib/ape/apetag.h +++ b/taglib/ape/apetag.h @@ -103,6 +103,25 @@ namespace TagLib { virtual void setYear(uint i); virtual void setTrack(uint i); + /*! + * Implements the unified tag dictionary interface -- export function. + * APE tags are perfectly compatible with the dictionary interface because they + * support both arbitrary tag names and multiple values. Currently only + * APE items of type *Text* are handled by the dictionary interface, while + * *Binary* and *Locator* items are simply ignored. + * + * The only conversion done by this export function is to rename the APE tags + * TRACK to TRACKNUMBER and YEAR to DATE, respectively, in order to be compliant + * with the names used in other formats. + */ + virtual TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. The same + * comments as for the export function apply. + */ + virtual void fromDict(const TagDict &); + /*! * Returns a pointer to the tag's footer. */ diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 715daf04..f67a71e7 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -262,11 +262,14 @@ namespace TagLib { /*! * Implements the unified tag dictionary interface -- export function. + * This function does some work to translate the hard-specified ID3v2 + * frame types into a free-form string-to-stringlist dictionary. */ virtual TagDict toDict() const; /*! * Implements the unified tag dictionary interface -- import function. + * See the comments in toDict(). */ virtual void fromDict(const TagDict &); diff --git a/taglib/ogg/xiphcomment.h b/taglib/ogg/xiphcomment.h index 988f616d..6ad23c6a 100644 --- a/taglib/ogg/xiphcomment.h +++ b/taglib/ogg/xiphcomment.h @@ -142,11 +142,16 @@ namespace TagLib { /*! * Implements the unified tag dictionary interface -- export function. + * The result is a one-to-one match of the Xiph comment, since it is + * completely compatible with the dictionary interface (in fact, a Xiph + * comment is nothing more than a map from tag names to list of values, + * as is the dict interface). */ virtual TagDict toDict() const; /*! * Implements the unified tag dictionary interface -- import function. + * The tags from the given dict will be stored one-to-one in the file. */ virtual void fromDict(const TagDict &); From 5647b2e2935d6e4a06f9fe8b449055772e31c097 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 28 Aug 2011 22:58:40 +0200 Subject: [PATCH 04/39] Made im/export functions nonvirtual. Added similar functions to File and its subclasses. TagLib::File contains a bunch of dynamic_casts to call the correct specializations. --- taglib/ape/apefile.cpp | 19 ++++++++++ taglib/ape/apefile.h | 13 +++++++ taglib/ape/apetag.h | 4 +-- taglib/flac/flacfile.cpp | 25 +++++++++++++ taglib/flac/flacfile.h | 17 ++++++++- taglib/mpc/mpcfile.cpp | 21 +++++++++++ taglib/mpc/mpcfile.h | 15 ++++++++ taglib/mpeg/id3v2/id3v2tag.h | 4 +-- taglib/mpeg/mpegfile.cpp | 25 +++++++++++++ taglib/mpeg/mpegfile.h | 16 +++++++++ taglib/ogg/flac/oggflacfile.cpp | 10 ++++++ taglib/ogg/flac/oggflacfile.h | 12 +++++++ taglib/ogg/speex/speexfile.cpp | 10 ++++++ taglib/ogg/speex/speexfile.h | 11 ++++++ taglib/ogg/vorbis/vorbisfile.cpp | 10 ++++++ taglib/ogg/vorbis/vorbisfile.h | 11 ++++++ taglib/ogg/xiphcomment.h | 4 +-- taglib/tag.h | 4 +-- taglib/toolkit/tfile.cpp | 60 ++++++++++++++++++++++++++++++++ taglib/toolkit/tfile.h | 15 ++++++++ 20 files changed, 297 insertions(+), 9 deletions(-) diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 2973a476..7c63412e 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -109,6 +109,25 @@ TagLib::Tag *APE::File::tag() const return &d->tag; } +TagLib::TagDict APE::File::toDict(void) const +{ + if (d->hasAPE) + return d->tag.access(APEIndex, false)->toDict(); + if (d->hasID3v1) + return d->tag.access(ID3v1Index, false)->toDict(); + return TagLib::TagDict(); +} + +void APE::File::fromDict(const TagDict &dict) +{ + if (d->hasAPE) + d->tag.access(APEIndex, false)->fromDict(dict); + else if (d->hasID3v1) + d->tag.access(ID3v1Index, false)->fromDict(dict); + else + d->tag.access(APE, true)->fromDict(dict); +} + APE::Properties *APE::File::audioProperties() const { return d->properties; diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index 2f22fdde..ab290b83 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -110,6 +110,19 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * If the file contains both an APE and an ID3v1 tag, only APE + * will be converted to the TagDict. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * As for the export, only one tag is taken into account. If the file + * has no tag at all, APE will be created. + */ + void fromDict(const TagDict &); /*! * Returns the APE::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ape/apetag.h b/taglib/ape/apetag.h index b48d9291..089420ea 100644 --- a/taglib/ape/apetag.h +++ b/taglib/ape/apetag.h @@ -114,13 +114,13 @@ namespace TagLib { * TRACK to TRACKNUMBER and YEAR to DATE, respectively, in order to be compliant * with the names used in other formats. */ - virtual TagDict toDict() const; + TagDict toDict() const; /*! * Implements the unified tag dictionary interface -- import function. The same * comments as for the export function apply. */ - virtual void fromDict(const TagDict &); + void fromDict(const TagDict &); /*! * Returns a pointer to the tag's footer. diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 5065cd29..ec925d0f 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -138,6 +138,31 @@ TagLib::Tag *FLAC::File::tag() const return &d->tag; } +TagLib::TagDict FLAC::File::toDict(void) const +{ + // once Tag::toDict() is virtual, this case distinction could actually be done + // within TagUnion. + if (d->hasXiphComment) + return d->tag.access(XiphIndex, false)->toDict(); + if (d->hasID3v2) + return d->tag.access(ID3v2Index, false)->toDict(); + if (d->hasID3v1) + return d->tag.access(ID3v1Index, false)->toDict(); + return TagLib::TagDict(); +} + +void FLAC::File::fromDict(const TagDict &dict) +{ + if (d->hasXiphComment) + d->tag.access(XiphIndex, false)->fromDict(dict); + else if (d->hasID3v2) + d->tag.access(ID3v2Index, false)->fromDict(dict); + else if (d->hasID3v1) + d->tag.access(ID3v1Index, false)->fromDict(dict); + else + d->tag.access(XiphIndex, true)->fromDict(dict); +} + FLAC::Properties *FLAC::File::audioProperties() const { return d->properties; diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 01466a2d..9fdc1c2e 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -29,6 +29,7 @@ #include "taglib_export.h" #include "tfile.h" #include "tlist.h" +#include "tag.h" #include "flacpicture.h" #include "flacproperties.h" @@ -36,7 +37,6 @@ namespace TagLib { class Tag; - namespace ID3v2 { class FrameFactory; class Tag; } namespace ID3v1 { class Tag; } namespace Ogg { class XiphComment; } @@ -118,6 +118,21 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * If the file contains more than one tag (e.g. XiphComment and ID3v1), + * only the first one (in the order XiphComment, ID3v2, ID3v1) will be + * converted to the TagDict. + */ + TagDict toDict() const; + + /*! + * 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, a XiphComment will be created. + */ + void fromDict(const TagDict &); + /*! * Returns the FLAC::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 216c1b3b..2482a90c 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -113,6 +113,27 @@ TagLib::Tag *MPC::File::tag() const return &d->tag; } +TagLib::TagDict MPC::File::toDict(void) 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(); +} + +void MPC::File::fromDict(const TagDict &dict) +{ + if (d->hasAPE) + d->tag.access(APEIndex, false)->fromDict(dict); + else if (d->hasID3v1) + d->tag.access(ID3v1Index, false)->fromDict(dict); + else + d->tag.access(APEIndex, true)->fromDict(dict); +} + MPC::Properties *MPC::File::audioProperties() const { return d->properties; diff --git a/taglib/mpc/mpcfile.h b/taglib/mpc/mpcfile.h index 93471cf1..6ff91e71 100644 --- a/taglib/mpc/mpcfile.h +++ b/taglib/mpc/mpcfile.h @@ -28,6 +28,7 @@ #include "taglib_export.h" #include "tfile.h" +#include "tag.h" #include "mpcproperties.h" @@ -107,6 +108,20 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * If the file contains both an APE and an ID3v1 tag, only the APE + * tag will be converted to the TagDict. + */ + TagDict toDict() const; + + /*! + * 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, APE will be created. + */ + void fromDict(const TagDict &); + /*! * Returns the MPC::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index f67a71e7..3f731282 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -265,13 +265,13 @@ namespace TagLib { * This function does some work to translate the hard-specified ID3v2 * frame types into a free-form string-to-stringlist dictionary. */ - virtual TagDict toDict() const; + TagDict toDict() const; /*! * Implements the unified tag dictionary interface -- import function. * See the comments in toDict(). */ - virtual void fromDict(const TagDict &); + void fromDict(const TagDict &); /*! * 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 a3bad823..645409fa 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -133,6 +133,31 @@ TagLib::Tag *MPEG::File::tag() const return &d->tag; } +TagLib::TagDict MPEG::File::toDict(void) const +{ + // once Tag::toDict() 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(); +} + +void MPEG::File::fromDict(const TagDict &dict) +{ + 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); + else + d->tag.access(ID3v2Index, true)->fromDict(dict); +} + MPEG::Properties *MPEG::File::audioProperties() const { return d->properties; diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index cff5469d..75da7b0a 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -28,6 +28,7 @@ #include "taglib_export.h" #include "tfile.h" +#include "tag.h" #include "mpegproperties.h" @@ -128,6 +129,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 + * first one (in the order ID3v2, APE, ID3v1) will be converted to the + * TagDict. + */ + TagDict toDict() const; + + /*! + * 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 &); + /*! * Returns the MPEG::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ogg/flac/oggflacfile.cpp b/taglib/ogg/flac/oggflacfile.cpp index 3addbffa..437dabf0 100644 --- a/taglib/ogg/flac/oggflacfile.cpp +++ b/taglib/ogg/flac/oggflacfile.cpp @@ -92,6 +92,16 @@ Ogg::XiphComment *Ogg::FLAC::File::tag() const return d->comment; } +TagLib::TagDict Ogg::FLAC::File::toDict(void) const +{ + return d->comment->toDict(); +} + +void Ogg::FLAC::File::fromDict(const TagDict &dict) +{ + d->comment->fromDict(dict); +} + Properties *Ogg::FLAC::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index d4373795..e39ac2cc 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -89,6 +89,18 @@ namespace TagLib { */ virtual XiphComment *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * Returns the contents of the Ogg::XiphComment as TagDict. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Matches the TagDict's contents to the XiphComment of the file. + */ + void fromDict(const TagDict &); + /*! * Returns the FLAC::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 3a4940a2..d602bcc8 100644 --- a/taglib/ogg/speex/speexfile.cpp +++ b/taglib/ogg/speex/speexfile.cpp @@ -82,6 +82,16 @@ Ogg::XiphComment *Speex::File::tag() const return d->comment; } +TagLib::TagDict Ogg::Speex::File::toDict(void) const +{ + return d->comment->toDict(); +} + +void Ogg::Speex::File::fromDict(const TagDict &dict) +{ + d->comment->fromDict(dict); +} + Speex::Properties *Speex::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index c14cf2aa..2af6cd82 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -82,6 +82,17 @@ namespace TagLib { * TagLib::File::tag(). */ virtual Ogg::XiphComment *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * Returns the contents of the Ogg::XiphComment as TagDict. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Matches the TagDict's contents to the XiphComment of the file. + */ + void fromDict(const TagDict &); /*! * Returns the Speex::Properties for this file. If no audio properties diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index 60056f83..fe50d6d0 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -85,6 +85,16 @@ Ogg::XiphComment *Vorbis::File::tag() const return d->comment; } +TagLib::TagDict Ogg::Vorbis::File::toDict(void) const +{ + return d->comment->toDict(); +} + +void Ogg::Vorbis::File::fromDict(const TagDict &dict) +{ + d->comment->fromDict(dict); +} + Vorbis::Properties *Vorbis::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index 299d9c2d..989eac3d 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -90,6 +90,17 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * Returns the contents of the Ogg::XiphComment as TagDict. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Matches the TagDict's contents to the XiphComment of the file. + */ + void fromDict(const TagDict &); /*! * Returns the Vorbis::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ogg/xiphcomment.h b/taglib/ogg/xiphcomment.h index 6ad23c6a..f9f23d54 100644 --- a/taglib/ogg/xiphcomment.h +++ b/taglib/ogg/xiphcomment.h @@ -147,13 +147,13 @@ namespace TagLib { * comment is nothing more than a map from tag names to list of values, * as is the dict interface). */ - virtual TagDict toDict() const; + TagDict toDict() const; /*! * Implements the unified tag dictionary interface -- import function. * The tags from the given dict will be stored one-to-one in the file. */ - virtual void fromDict(const TagDict &); + void fromDict(const TagDict &); /*! * Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the diff --git a/taglib/tag.h b/taglib/tag.h index 45caf083..728c3980 100644 --- a/taglib/tag.h +++ b/taglib/tag.h @@ -63,7 +63,7 @@ namespace TagLib { * of the specific metadata format into a "human-readable" map of strings * to lists of strings, being as precise as possible. */ - virtual TagDict toDict() const; + TagDict toDict() const; /*! * Unified tag dictionary interface -- import function. Converts a map @@ -72,7 +72,7 @@ namespace TagLib { * be lost by this operation. Especially the default implementation handles * only single values of the default tags specified in this class. */ - virtual void fromDict(const TagDict &); + void fromDict(const TagDict &); /*! * Returns the track name; if no track name is present in the tag diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index ae3eec1d..9ac5f9bb 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -50,6 +50,24 @@ # define W_OK 2 #endif +#include "asffile.h" +#include "mpegfile.h" +#include "vorbisfile.h" +#include "flacfile.h" +#include "oggflacfile.h" +#include "mpcfile.h" +#include "mp4file.h" +#include "wavpackfile.h" +#include "speexfile.h" +#include "trueaudiofile.h" +#include "aifffile.h" +#include "wavfile.h" +#include "apefile.h" +#include "modfile.h" +#include "s3mfile.h" +#include "itfile.h" +#include "xmfile.h" \ + using namespace TagLib; class File::FilePrivate @@ -95,6 +113,48 @@ FileName File::name() const return d->stream->name(); } +TagDict File::toDict() const +{ + // ugly workaround until this method is virtual + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + // no specialized implementation available -> use generic one + return tag()->toDict(); +} + +void File::fromDict(const TagDict &dict) +{ + if (dynamic_cast(this)) + dynamic_cast< APE::File* >(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast< FLAC::File* >(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast< MPC::File* >(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast< MPEG::File* >(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast< Ogg::FLAC::File* >(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast< Ogg::Speex::File* >(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast< Ogg::Vorbis::File* >(this)->fromDict(dict); + else + tag()->fromDict(dict); + +} + ByteVector File::readBlock(ulong length) { return d->stream->readBlock(length); diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index ee6f0488..75faf8a8 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -28,6 +28,7 @@ #include "taglib_export.h" #include "taglib.h" +#include "tag.h" #include "tbytevector.h" #include "tiostream.h" @@ -76,6 +77,20 @@ namespace TagLib { */ virtual Tag *tag() const = 0; + /*! + * Exports the tags of the file as dictionary mapping (human readable) tag + * names (Strings) to StringLists of tag values. Calls the according specialization + * in the File subclasses. + * Will be made virtual in future releases. + */ + TagDict toDict() const; + + /*! + * Sets the tags of this File to those specified by the given TagDict. Calls the + * according specialization method in the subclasses of File to do the translation + * into the format-specific details. + */ + void fromDict(const TagDict &); /*! * Returns a pointer to this file's audio properties. This should be * reimplemented in the concrete subclasses. If no audio properties were From 2d310750477d2427622d6e18855cdbd1e71df4bc Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 11 Sep 2011 18:22:15 +0200 Subject: [PATCH 05/39] Splitted ID3v2Tag::toDict() into several functions. This should simplify future transition to virtual functions. --- taglib/mpeg/id3v2/id3v2dicttools.cpp | 152 +++++++++++++++++++++++---- taglib/mpeg/id3v2/id3v2dicttools.h | 27 ++++- taglib/mpeg/id3v2/id3v2tag.cpp | 104 +++--------------- 3 files changed, 174 insertions(+), 109 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp index 903c9372..9fef9ad0 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.cpp +++ b/taglib/mpeg/id3v2/id3v2dicttools.cpp @@ -25,6 +25,35 @@ #include "tdebug.h" #include "id3v2dicttools.h" #include "tmap.h" + +#include "frames/textidentificationframe.h" +#include "frames/commentsframe.h" +#include "frames/urllinkframe.h" +#include "frames/uniquefileidentifierframe.h" +#include "frames/unsynchronizedlyricsframe.h" +#include "id3v1genres.h" + +using namespace TagLib; +using namespace ID3v2; + +// 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; +} + namespace TagLib { namespace ID3v2 { @@ -110,16 +139,9 @@ namespace TagLib { "UFID", // unique file identifier }; - // 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 - }; - String frameIDToTagName(const ByteVector &id) { + String frameIDToTagName(const ByteVector &id) + { static Map m; if (m.isEmpty()) for (size_t i = 0; i < numid3frames; ++i) @@ -133,7 +155,8 @@ namespace TagLib { return "UNKNOWNID3TAG"; //TODO: implement this nicer } - ByteVector tagNameToFrameID(const String &s) { + ByteVector tagNameToFrameID(const String &s) + { static Map m; if (m.isEmpty()) for (size_t i = 0; i < numid3frames; ++i) @@ -143,7 +166,8 @@ namespace TagLib { return "TXXX"; } - bool isIgnored(const ByteVector& id) { + bool isIgnored(const ByteVector& id) + { List ignoredList; if (ignoredList.isEmpty()) for (uint i = 0; i < ignoredFramesSize; ++i) @@ -151,16 +175,106 @@ namespace TagLib { return ignoredList.contains(id); } - FrameIDMap deprecationMap() { - static FrameIDMap depMap; - if (depMap.isEmpty()) - for(uint i = 0; i < deprecatedFramesSize; ++i) - depMap[deprecatedFrames[i][0]] = deprecatedFrames[i][1]; - return depMap; + + + bool isDeprecated(const ByteVector& id) + { + return deprecationMap().contains(id); } - bool isDeprecated(const ByteVector& id) { - return deprecationMap().contains(id); + /* + * The following _parseXXX functions are to be replaced by implementations of a virtual + * function in ID3v2::Frame ASAP. + */ + KeyValuePair _parseUserTextIdentificationFrame(const UserTextIdentificationFrame *frame) + { + String tagName = frame->description(); + StringList l(frame->fieldList()); + // this is done because taglib stores the description also as first entry + // in the field list. (why?) + if (l.contains(tagName)) + l.erase(l.find(tagName)); + // handle user text frames set by the QuodLibet / exFalso package, + // which sets the description to QuodLibet:: instead of simply + // . + int pos = tagName.find("::"); + tagName = (pos != -1) ? tagName.substr(pos+2) : tagName; + return KeyValuePair(tagName.upper(), l); + } + + KeyValuePair _parseTextIdentificationFrame(const TextIdentificationFrame *frame) + { + String tagName = frameIDToTagName(frame->frameID()); + StringList l = frame->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 lit = l.begin(); lit != l.end(); ++lit) { + bool ok = false; + int test = lit->toInt(&ok); // test if the genre value is an integer + if (ok) + *lit = ID3v1::genre(test); + } + } + else if (tagName == "DATE") { + for (StringList::Iterator lit = l.begin(); lit != l.end(); ++lit) { + // 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 = lit->find("T"); + if (tpos != -1) + (*lit)[tpos] = ' '; + } + } + return KeyValuePair(tagName, l); + } + + KeyValuePair _parseUserUrlLinkFrame(const UserUrlLinkFrame *frame) + { + String tagName = frame->description().upper(); + if (tagName == "") + tagName = "URL"; + return KeyValuePair(tagName, frame->url()); + } + + KeyValuePair _parseUrlLinkFrame(const UrlLinkFrame *frame) + { + return KeyValuePair(frameIDToTagName(frame->frameID()) , frame->url()); + } + + KeyValuePair _parseCommentsFrame(const CommentsFrame *frame) + { + String tagName = frame->description().upper(); + if (tagName.isEmpty()) + tagName = "COMMENT"; + return KeyValuePair(tagName, frame->text()); + } + + KeyValuePair _parseUnsynchronizedLyricsFrame(const UnsynchronizedLyricsFrame *frame) + { + return KeyValuePair("LYRICS", frame->text()); + } + + KeyValuePair parseFrame(const Frame *frame) + { + const ByteVector &id = frame->frameID(); + if (id == "TXXX") + return _parseUserTextIdentificationFrame(dynamic_cast< const UserTextIdentificationFrame* >(frame)); + else if (id[0] == 'T') + return _parseTextIdentificationFrame(dynamic_cast(frame)); + else if (id == "WXXX") + return _parseUserUrlLinkFrame(dynamic_cast< const UserUrlLinkFrame* >(frame)); + else if (id[0] == 'W') + return _parseUrlLinkFrame(dynamic_cast< const UrlLinkFrame* >(frame)); + else if (id == "COMM") + return _parseCommentsFrame(dynamic_cast< const CommentsFrame* >(frame)); + else if (id == "USLT") + return _parseUnsynchronizedLyricsFrame(dynamic_cast< const UnsynchronizedLyricsFrame* >(frame)); + else { + debug("parsing unknown ID3 frame: " + id); + return KeyValuePair("UNKNOWNID3TAG", frame->toString()); + } } } } diff --git a/taglib/mpeg/id3v2/id3v2dicttools.h b/taglib/mpeg/id3v2/id3v2dicttools.h index 3e7a329f..7dfdda2f 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.h +++ b/taglib/mpeg/id3v2/id3v2dicttools.h @@ -29,6 +29,7 @@ #include "tstringlist.h" #include "taglib_export.h" #include "tmap.h" +#include namespace TagLib { namespace ID3v2 { @@ -36,18 +37,38 @@ namespace TagLib { * This file contains methods used by the unified dictionary interface for ID3v2 tags * (tag name conversion, handling of un-translatable frameIDs, ...). */ - typedef Map FrameIDMap; + typedef Map FrameIDMap; + typedef std::pair KeyValuePair; + + // forward declaration + class Frame; + /*! + * Returns an appropriate ID3 frame ID for the given free-form tag name. + */ ByteVector TAGLIB_EXPORT tagNameToFrameID(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. + */ String TAGLIB_EXPORT frameIDToTagName(const ByteVector &); + /*! + * Tell if the given frame ID is ignored by the unified dictionary subsystem. This is true + * for frames that don't admit a textual representation, such as pictures or other binary + * information. + */ bool TAGLIB_EXPORT isIgnored(const ByteVector &); - FrameIDMap TAGLIB_EXPORT deprecationMap(); - bool TAGLIB_EXPORT isDeprecated(const ByteVector&); + /*! + * Parse the ID3v2::Frame *Frame* to a pair of a human-readable key (e.g. ARTIST) and + * a StringList containing the values. + */ + KeyValuePair parseFrame(const Frame*); + } } diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 83406cdb..29dce9ad 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -340,92 +340,16 @@ TagDict ID3v2::Tag::toDict() const for (; frameIt != frameList().end(); ++frameIt) { ByteVector id = (*frameIt)->frameID(); - if (isIgnored(id)) { - debug("found ignored id3 frame " + id); - continue; + if (isIgnored(id)) + debug("toDict() found ignored id3 frame: " + id); + else if (isDeprecated(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(*frameIt); + dict[kvp.first].append(kvp.second); } - if (isDeprecated(id)) { - debug("found deprecated id3 frame " + id); - continue; - } - if (id[0] == 'T') { - if (id == "TXXX") { - const UserTextIdentificationFrame *uframe - = dynamic_cast< const UserTextIdentificationFrame* >(*frameIt); - String tagName = uframe->description(); - StringList l(uframe->fieldList()); - // this is done because taglib stores the description also as first entry - // in the field list. (why?) - // - if (l.contains(tagName)) - l.erase(l.find(tagName)); - // handle user text frames set by the QuodLibet / exFalso package, - // which sets the description to QuodLibet:: instead of simply - // . - int pos = tagName.find("::"); - tagName = (pos != -1) ? tagName.substr(pos+2) : tagName; - dict[tagName.upper()].append(l); - } - else { - const TextIdentificationFrame* tframe - = dynamic_cast< const TextIdentificationFrame* >(*frameIt); - String tagName = frameIDToTagName(id); - StringList l = tframe->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 lit = l.begin(); lit != l.end(); ++lit) { - bool ok = false; - int test = lit->toInt(&ok); // test if the genre value is an integer - if (ok) { - *lit = ID3v1::genre(test); - } - } - } - else if (tagName == "DATE") { - for (StringList::Iterator lit = l.begin(); lit != l.end(); ++lit) { - // 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 = lit->find("T"); - if (tpos != -1) - (*lit)[tpos] = ' '; - } - } - dict[tagName].append(l); - } - continue; - } - if (id[0] == 'W') { - if (id == "WXXX") { - const UserUrlLinkFrame *uframe = dynamic_cast< const UserUrlLinkFrame* >(*frameIt); - String tagname = uframe->description().upper(); - if (tagname == "") - tagname = "URL"; - dict[tagname].append(uframe->url()); - } - else { - const UrlLinkFrame* uframe = dynamic_cast< const UrlLinkFrame* >(*frameIt); - dict[frameIDToTagName(id)].append(uframe->url()); - } - continue; - } - if (id == "COMM") { - const CommentsFrame *cframe = dynamic_cast< const CommentsFrame* >(*frameIt); - String tagName = cframe->description().upper(); - if (tagName.isEmpty()) - tagName = "COMMENT"; - dict[tagName].append(cframe->text()); - continue; - } - if (id == "USLT") { - const UnsynchronizedLyricsFrame *uframe - = dynamic_cast< const UnsynchronizedLyricsFrame* >(*frameIt); - dict["LYRICS"].append(uframe->text()); - continue; - } - debug("unknown frame ID: " + id); } return dict; } @@ -437,15 +361,21 @@ void ID3v2::Tag::fromDict(const TagDict &dict) // because that would invalidate FrameListMap iterators. // for (FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) { - if (it->second.size() == 0) // ignore empty map entries (does this ever happen?) + // ignore empty map entries (does this ever happen?) + if (it->second.size() == 0) continue; - if (isDeprecated(it->first))// automatically remove deprecated frames + + // automatically remove deprecated frames + else if (isDeprecated(it->first)) toRemove.append(it->second); else if (it->first == "TXXX") { // handle user text frames specially for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { UserTextIdentificationFrame* frame = dynamic_cast< UserTextIdentificationFrame* >(*fit); String tagName = frame->description(); + // handle user text frames set by the QuodLibet / exFalso package, + // which sets the description to QuodLibet:: instead of simply + // . int pos = tagName.find("::"); tagName = (pos == -1) ? tagName : tagName.substr(pos+2); if (!dict.contains(tagName.upper())) From 0c2ca20ec2b2ca316ec379d92a904c310c0d6bf6 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 11 Sep 2011 22:07:49 +0200 Subject: [PATCH 06/39] Restructured and simplified ID3v2Tag::fromDict(). --- taglib/mpeg/id3v2/id3v2dicttools.cpp | 89 ++++++++++++- taglib/mpeg/id3v2/id3v2dicttools.h | 13 +- taglib/mpeg/id3v2/id3v2tag.cpp | 186 ++------------------------- 3 files changed, 104 insertions(+), 184 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp index 9fef9ad0..172b4afb 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.cpp +++ b/taglib/mpeg/id3v2/id3v2dicttools.cpp @@ -182,6 +182,10 @@ namespace TagLib { return deprecationMap().contains(id); } + String prepareTagName(const String &s) { + int pos = s.find("::"); + return ((pos != -1) ? s.substr(pos+2) : s).upper(); + } /* * The following _parseXXX functions are to be replaced by implementations of a virtual * function in ID3v2::Frame ASAP. @@ -194,12 +198,15 @@ namespace TagLib { // in the field list. (why?) if (l.contains(tagName)) l.erase(l.find(tagName)); - // handle user text frames set by the QuodLibet / exFalso package, - // which sets the description to QuodLibet:: instead of simply - // . - int pos = tagName.find("::"); - tagName = (pos != -1) ? tagName.substr(pos+2) : tagName; - return KeyValuePair(tagName.upper(), l); + return KeyValuePair(prepareTagName(tagName), l); + } + + Frame *_createUserTextIdentificationFrame(const String &tag, const StringList &values) + { + UserTextIdentificationFrame* frame = new UserTextIdentificationFrame(); + frame->setDescription(tag); + frame->setText(values); + return frame; } KeyValuePair _parseTextIdentificationFrame(const TextIdentificationFrame *frame) @@ -230,6 +237,21 @@ namespace TagLib { return KeyValuePair(tagName, l); } + Frame *_createTextIdentificationFrame(const String &tag, const StringList &values) + { + StringList newValues(values); // create a copy because the following might modify + // the easiest case: a normal text frame + if (tag == "DATE") { + // Handle ISO8601 date format + for (StringList::Iterator lit = newValues.begin(); lit != newValues.end(); ++lit) + if (lit->length() > 10 && (*lit)[10] == ' ') + (*lit)[10] = 'T'; + } + TextIdentificationFrame *frame = new TextIdentificationFrame(tagNameToFrameID(tag)); + frame->setText(newValues); + return frame; + } + KeyValuePair _parseUserUrlLinkFrame(const UserUrlLinkFrame *frame) { String tagName = frame->description().upper(); @@ -238,11 +260,32 @@ namespace TagLib { return KeyValuePair(tagName, frame->url()); } + /*! + * Create a UserUrlLinkFrame. Note that this is valid only if values.size() == 1. + */ + Frame *_createUserUrlLinkFrame(const String &tag, const StringList &values) + { + UserUrlLinkFrame* frame = new UserUrlLinkFrame(); + frame->setDescription(tag); + frame->setUrl(values[0]); + return frame; + } + KeyValuePair _parseUrlLinkFrame(const UrlLinkFrame *frame) { return KeyValuePair(frameIDToTagName(frame->frameID()) , frame->url()); } + /*! + * Create a rUrlLinkFrame. Note that this is valid only if values.size() == 1. + */ + Frame *_createUrlLinkFrame(const String &tag, const StringList &values) + { + UrlLinkFrame *frame = new UrlLinkFrame(tagNameToFrameID(tag)); + frame->setUrl(values[0]); + return frame; + } + KeyValuePair _parseCommentsFrame(const CommentsFrame *frame) { String tagName = frame->description().upper(); @@ -251,11 +294,26 @@ namespace TagLib { return KeyValuePair(tagName, frame->text()); } + Frame *_createCommentsFrame(const String &tag, const StringList &values) + { + CommentsFrame *frame = new CommentsFrame(String::UTF8); + frame->setText(values[0]); + return frame; + } + KeyValuePair _parseUnsynchronizedLyricsFrame(const UnsynchronizedLyricsFrame *frame) { return KeyValuePair("LYRICS", frame->text()); } + Frame *_createUnsynchronizedLyricsFrame(const String &tag, const StringList &values) + { + UnsynchronizedLyricsFrame* frame = new UnsynchronizedLyricsFrame(); + frame->setDescription(""); + frame->setText(values[0]); + return frame; + } + KeyValuePair parseFrame(const Frame *frame) { const ByteVector &id = frame->frameID(); @@ -276,5 +334,24 @@ namespace TagLib { return KeyValuePair("UNKNOWNID3TAG", frame->toString()); } } + + Frame *createFrame(const String &tag, const StringList &values) + { + ByteVector id = tagNameToFrameID(tag); + if (id == "TXXX" || + ((id[0] == 'W' || id == "COMM" || id == "USLT") && values.size() > 1)) + return _createUserTextIdentificationFrame(tag, values); + else if (id[0] == 'T') + return _createTextIdentificationFrame(tag, values); + else if (id == "WXXX") + return _createUserUrlLinkFrame(tag, values); + else if (id[0] == 'W') + return _createUrlLinkFrame(tag, values); + else if (id == "COMM") + return _createCommentsFrame(tag, values); + else if (id == "USLT") + return _createUnsynchronizedLyricsFrame(tag, values); + return 0; + } } } diff --git a/taglib/mpeg/id3v2/id3v2dicttools.h b/taglib/mpeg/id3v2/id3v2dicttools.h index 7dfdda2f..5963618a 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.h +++ b/taglib/mpeg/id3v2/id3v2dicttools.h @@ -44,7 +44,8 @@ namespace TagLib { // forward declaration class Frame; /*! - * Returns an appropriate ID3 frame ID for the given free-form tag name. + * Returns an appropriate ID3 frame ID for the given free-form tag name. This method + * will return TXXX if no specialized translation is found. */ ByteVector TAGLIB_EXPORT tagNameToFrameID(const String &); @@ -69,6 +70,16 @@ namespace TagLib { */ KeyValuePair parseFrame(const Frame*); + /*! + * Create an appropriate ID3v2::Frame for the given tag name and values. + */ + Frame *createFrame(const String &tag, const StringList &values); + /*! + * prepare the given tag name for use in a unified dictionary: make it uppercase and + * removes prefixes set by the ExFalso/QuodLibet package. + */ + String prepareTagName(const String &); + } } diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 29dce9ad..84c89a47 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -357,192 +357,24 @@ TagDict ID3v2::Tag::toDict() const void ID3v2::Tag::fromDict(const TagDict &dict) { FrameList toRemove; - // first record what frames to remove; we do not remove in-place + // first find out what frames to remove; we do not remove in-place // because that would invalidate FrameListMap iterators. // - for (FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) { - // ignore empty map entries (does this ever happen?) - if (it->second.size() == 0) - continue; + for (FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) + // Remove all frames which are not ignored + if (it->second.size() == 0 || !isIgnored(it->first)) + toRemove.append(it->second); - // automatically remove deprecated frames - else if (isDeprecated(it->first)) - toRemove.append(it->second); - else if (it->first == "TXXX") { // handle user text frames specially - for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { - UserTextIdentificationFrame* frame - = dynamic_cast< UserTextIdentificationFrame* >(*fit); - String tagName = frame->description(); - // handle user text frames set by the QuodLibet / exFalso package, - // which sets the description to QuodLibet:: instead of simply - // . - int pos = tagName.find("::"); - tagName = (pos == -1) ? tagName : tagName.substr(pos+2); - if (!dict.contains(tagName.upper())) - toRemove.append(frame); - } - } - else if (it->first == "WXXX") { // handle user URL frames specially - for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { - UserUrlLinkFrame* frame = dynamic_cast(*fit); - String tagName = frame->description().upper(); - if (!(tagName == "URL") || !dict.contains("URL") || dict["URL"].size() > 1) - toRemove.append(frame); - } - } - else if (it->first == "COMM") { - for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { - CommentsFrame* frame = dynamic_cast< CommentsFrame* >(*fit); - String tagName = frame->description().upper(); - // policy: use comment frame only with empty description and only if a comment tag - // is present in the dictionary and only if there's no more than one comment - // (COMM is not specified for multiple values) - if ( !(tagName == "") || !dict.contains("COMMENT") || dict["COMMENT"].size() > 1) - toRemove.append(frame); - } - } - else if (it->first == "USLT") { - for (FrameList::ConstIterator fit = it->second.begin(); fit != it->second.end(); ++fit) { - UnsynchronizedLyricsFrame *frame - = dynamic_cast< UnsynchronizedLyricsFrame* >(*fit); - String tagName = frame->description().upper(); - if ( !(tagName == "") || !dict.contains("LYRICS") || dict["LYRICS"].size() > 1) - toRemove.append(frame); - } - } - else if (it->first[0] == 'T') { // a normal text frame - if (!dict.contains(frameIDToTagName(it->first))) - toRemove.append(it->second); - - } else - debug("file contains unknown tag" + it->first + ", not touching it..."); - } - - // now remove the frames that have been determined above for (FrameList::ConstIterator it = toRemove.begin(); it != toRemove.end(); it++) removeFrame(*it); - // now sync in the "forward direction" + // now create new frames from the TagDict and add them. for (TagDict::ConstIterator it = dict.begin(); it != dict.end(); ++it) { - const String &tagName = it->first; - ByteVector id = tagNameToFrameID(tagName); - if (id[0] == 'T' && id != "TXXX") { - // the easiest case: a normal text frame - StringList values = it->second; - const FrameList &framelist = frameList(id); - if (tagName == "DATE") { - // Handle ISO8601 date format (see above) - for (StringList::Iterator lit = values.begin(); lit != values.end(); ++lit) { - if (lit->length() > 10 && (*lit)[10] == ' ') - (*lit)[10] = 'T'; - } - } - if (framelist.size() > 0) { // there exists already a frame for this tag - const TextIdentificationFrame *frame = dynamic_cast(framelist[0]); - if (values == frame->fieldList()) - continue; // equal tag values -> everything ok - } - // if there was no frame for this tag, or there was one but the values aren't equal, - // we start from scratch and create a new one - // - removeFrames(id); - TextIdentificationFrame *frame = new TextIdentificationFrame(id); - frame->setText(values); - addFrame(frame); - } - else if (id == "TXXX" || - ((id == "WXXX" || id == "COMM" || id == "USLT") && it->second.size() > 1)) { - // In all those cases, we store the tag as TXXX frame. - // First we search for existing TXXX frames with correct description - FrameList existingFrames; - FrameList l = frameList("TXXX"); - - for (FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) { - String desc= dynamic_cast< UserTextIdentificationFrame* >(*fit)->description(); - int pos = desc.find("::"); - String tagName = (pos == -1) ? desc.upper() : desc.substr(pos+2).upper(); - if (tagName == it->first) - existingFrames.append(*fit); - } - - bool needsInsert = false; - if (existingFrames.size() > 1) { //several tags with same key, remove all and reinsert - for (FrameList::ConstIterator it = existingFrames.begin(); it != existingFrames.end(); ++it) - removeFrame(*it); - needsInsert = true; - } - else if (existingFrames.isEmpty()) // no frame -> needs insert - needsInsert = true; - else { - if (!(dynamic_cast< UserTextIdentificationFrame*>(existingFrames[0])->fieldList() == it->second)) { - needsInsert = true; - removeFrame(existingFrames[0]); - } - } - if (needsInsert) { // create and insert new frame - UserTextIdentificationFrame* frame = new UserTextIdentificationFrame(); - frame->setDescription(it->first); - frame->setText(it->second); - addFrame(frame); - } - } - else if (id == "WXXX") { - // we know that it->second.size()==1, since the other cases are handled above - bool needsInsert = true; - FrameList existingFrames = frameList(id); - if (existingFrames.size() > 1 ) // do not allow several WXXX frames - removeFrames(id); - else if (existingFrames.size() == 1) { - needsInsert = !(dynamic_cast< UserUrlLinkFrame* >(existingFrames[0])->url() == it->second[0]); - if (needsInsert) - removeFrames(id); - } - if (needsInsert) { - UserUrlLinkFrame* frame = new ID3v2::UserUrlLinkFrame(); - frame->setDescription(it->first); - frame->setUrl(it->second[0]); - addFrame(frame); - } - } - else if (id == "COMM") { - FrameList existingFrames = frameList(id); - bool needsInsert = true; - if (existingFrames.size() > 1) // do not allow several COMM frames - removeFrames(id); - else if (existingFrames.size() == 1) { - needsInsert = !(dynamic_cast< CommentsFrame* >(existingFrames[0])->text() == it->second[0]); - if (needsInsert) - removeFrames(id); - } - - if (needsInsert) { - CommentsFrame* frame = new CommentsFrame(); - frame->setDescription(""); // most software players use empty description COMM frames for comments - frame->setText(it->second[0]); - addFrame(frame); - } - } - else if (id == "USLT") { - FrameList existingFrames = frameList(id); - bool needsInsert = true; - if (existingFrames.size() > 1) // do not allow several USLT frames - removeFrames(id); - else if (existingFrames.size() == 1) { - needsInsert = !(dynamic_cast< UnsynchronizedLyricsFrame* >(existingFrames[0])->text() == it->second[0]); - if (needsInsert) - removeFrames(id); - } - - if (needsInsert) { - UnsynchronizedLyricsFrame* frame = new UnsynchronizedLyricsFrame(); - frame->setDescription(""); - frame->setText(it->second[0]); - addFrame(frame); - } - } + Frame *newFrame = createFrame(it->first, it->second); + if (newFrame) + addFrame(newFrame); else debug("ERROR: Don't know how to translate tag " + it->first + " to ID3v2!"); - } } From 772bc9f7c4f3d0024bd7bc3b8f4ae4afcbd94337 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Mon, 12 Sep 2011 21:52:11 +0200 Subject: [PATCH 07/39] Further cleanup and simplification in id3v2dicttools --- taglib/mpeg/id3v2/id3v2dicttools.cpp | 81 +++++++++++----------------- taglib/mpeg/id3v2/id3v2dicttools.h | 23 ++++++-- taglib/mpeg/id3v2/id3v2tag.cpp | 7 +-- 3 files changed, 55 insertions(+), 56 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp index 172b4afb..498f92ed 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.cpp +++ b/taglib/mpeg/id3v2/id3v2dicttools.cpp @@ -33,30 +33,27 @@ #include "frames/unsynchronizedlyricsframe.h" #include "id3v1genres.h" -using namespace TagLib; -using namespace ID3v2; - -// 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; -} - namespace TagLib { namespace ID3v2 { + // 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; + } + /*! * A map of translations frameID <-> tag used by the unified dictionary interface. */ @@ -127,32 +124,24 @@ namespace TagLib { { "USLT", "LYRICS" }, }; - // list of frameIDs that are ignored by the unified dictionary interface - static const uint ignoredFramesSize = 7; - static const char *ignoredFrames[] = { - "TCMP", // illegal 'Part of Compilation' frame set by iTunes (see http://www.id3.org/Compliance_Issues) - "GEOB", // no way to handle a general encapsulated object by the dict interface - "PRIV", // private frames - "APIC", // attached picture -- TODO how could we do this? - "POPM", // popularimeter - "RVA2", // relative volume - "UFID", // unique file identifier - }; - + Map &idMap() + { + static Map m; + if (m.isEmpty()) + for (size_t i = 0; i < numid3frames; ++i) + m[id3frames[i][0]] = id3frames[i][1]; + return m; + } String frameIDToTagName(const ByteVector &id) { - static Map m; - if (m.isEmpty()) - for (size_t i = 0; i < numid3frames; ++i) - m[id3frames[i][0]] = id3frames[i][1]; - + Map &m = idMap(); if (m.contains(id)) return m[id]; if (deprecationMap().contains(id)) return m[deprecationMap()[id]]; - debug("unknown frame ID: " + id); - return "UNKNOWNID3TAG"; //TODO: implement this nicer + debug("unknown frame ID in frameIDToTagName(): " + id); + return "UNKNOWNID3TAG#" + String(id) + "#"; //TODO: implement this nicer } ByteVector tagNameToFrameID(const String &s) @@ -166,18 +155,12 @@ namespace TagLib { return "TXXX"; } - bool isIgnored(const ByteVector& id) + bool ignored(const ByteVector& id) { - List ignoredList; - if (ignoredList.isEmpty()) - for (uint i = 0; i < ignoredFramesSize; ++i) - ignoredList.append(ignoredFrames[i]); - return ignoredList.contains(id); + return !(id == "TXXX") && !idMap().contains(id) && !deprecated(id); } - - - bool isDeprecated(const ByteVector& id) + bool deprecated(const ByteVector& id) { return deprecationMap().contains(id); } diff --git a/taglib/mpeg/id3v2/id3v2dicttools.h b/taglib/mpeg/id3v2/id3v2dicttools.h index 5963618a..1f831c63 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.h +++ b/taglib/mpeg/id3v2/id3v2dicttools.h @@ -41,7 +41,6 @@ namespace TagLib { typedef Map FrameIDMap; typedef std::pair KeyValuePair; - // forward declaration class Frame; /*! * Returns an appropriate ID3 frame ID for the given free-form tag name. This method @@ -58,11 +57,27 @@ namespace TagLib { /*! * Tell if the given frame ID is ignored by the unified dictionary subsystem. This is true * for frames that don't admit a textual representation, such as pictures or other binary - * information. + * information, as well as invalid frames that violate the ID3 specification. + * + * These include: + * - illegal frames violating the specification but seem to be used by some applications, e.g. + * "TCMP", illegal 'Part of Compilation' frame set by iTunes (see http://www.id3.org/Compliance_Issues) + * "NCON", illegal MusicMatch frame (see http://www.id3.org/Compliance_Issues) + * + * - frames without a meaningful textual representation -- might be implemented in some future release + * "GEOB", no way to handle a general encapsulated object by the dict interface + * "PRIV", private frames + * "APIC", attached picture + * "POPM", popularimeter + * "RVA2", relative volume + * "UFID", unique file identifier */ - bool TAGLIB_EXPORT isIgnored(const ByteVector &); + bool TAGLIB_EXPORT ignored(const ByteVector &); - bool TAGLIB_EXPORT isDeprecated(const ByteVector&); + /*! + * Returns true if the given frame ID is deprecated according to the most recent ID3v2 standard. + */ + bool TAGLIB_EXPORT deprecated(const ByteVector&); /*! * Parse the ID3v2::Frame *Frame* to a pair of a human-readable key (e.g. ARTIST) and diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 84c89a47..9c5e50f4 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -40,6 +40,7 @@ #include "frames/urllinkframe.h" #include "frames/uniquefileidentifierframe.h" #include "frames/unsynchronizedlyricsframe.h" +#include "frames/unknownframe.h" using namespace TagLib; using namespace ID3v2; @@ -340,9 +341,9 @@ TagDict ID3v2::Tag::toDict() const for (; frameIt != frameList().end(); ++frameIt) { ByteVector id = (*frameIt)->frameID(); - if (isIgnored(id)) + if (ignored(id)) debug("toDict() found ignored id3 frame: " + id); - else if (isDeprecated(id)) + else if (deprecated(id)) debug("toDict() found deprecated id3 frame: " + id); else { // in the future, something like dict[frame->tagName()].append(frame->values()) @@ -362,7 +363,7 @@ void ID3v2::Tag::fromDict(const TagDict &dict) // for (FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) // Remove all frames which are not ignored - if (it->second.size() == 0 || !isIgnored(it->first)) + if (it->second.size() == 0 || !ignored(it->first) ) toRemove.append(it->second); for (FrameList::ConstIterator it = toRemove.begin(); it != toRemove.end(); it++) From 0eaf3a3fbdca2f24e4a4277151edf7a66b7becaf Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Wed, 2 Nov 2011 21:02:35 +0100 Subject: [PATCH 08/39] Implemented dict interface for more formats. Now supported: MOD files (IT, MOD, S3M, XM), RIFF files (AIFF, WAV), TrueAudio, WavPack. --- taglib/it/itfile.cpp | 10 ++++ taglib/it/itfile.h | 13 +++++ taglib/mod/modfile.cpp | 10 ++++ taglib/mod/modfile.h | 11 +++++ taglib/mod/modtag.cpp | 30 +++++++++++- taglib/mod/modtag.h | 15 ++++++ taglib/riff/aiff/aifffile.cpp | 13 +++++ taglib/riff/aiff/aifffile.h | 12 +++++ taglib/riff/wav/wavfile.cpp | 12 +++++ taglib/riff/wav/wavfile.h | 12 +++++ taglib/s3m/s3mfile.cpp | 10 ++++ taglib/s3m/s3mfile.h | 12 +++++ taglib/tag.cpp | 14 +++--- taglib/toolkit/tfile.cpp | 79 ++++++++++++++++++++++-------- taglib/trueaudio/trueaudiofile.cpp | 22 +++++++++ taglib/trueaudio/trueaudiofile.h | 14 ++++++ taglib/wavpack/wavpackfile.cpp | 19 +++++++ taglib/wavpack/wavpackfile.h | 14 ++++++ taglib/xm/xmfile.cpp | 10 ++++ taglib/xm/xmfile.h | 12 +++++ 20 files changed, 315 insertions(+), 29 deletions(-) diff --git a/taglib/it/itfile.cpp b/taglib/it/itfile.cpp index 5f72d3d9..5815b98a 100644 --- a/taglib/it/itfile.cpp +++ b/taglib/it/itfile.cpp @@ -65,6 +65,16 @@ Mod::Tag *IT::File::tag() const return &d->tag; } +TagDict IT::File::toDict() const +{ + return d->tag.toDict(); +} + +void IT::File::fromDict(const TagDict &tagDict) +{ + d->tag.fromDict(tagDict); +} + IT::Properties *IT::File::audioProperties() const { return &d->properties; diff --git a/taglib/it/itfile.h b/taglib/it/itfile.h index 73256d32..e4a744e9 100644 --- a/taglib/it/itfile.h +++ b/taglib/it/itfile.h @@ -60,6 +60,18 @@ namespace TagLib { Mod::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * Forwards to Mod::Tag::toDict(). + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Forwards to Mod::Tag::fromDict(). + */ + void fromDict(const TagDict &); + /*! * Returns the IT::Properties for this file. If no audio properties * were read then this will return a null pointer. @@ -74,6 +86,7 @@ namespace TagLib { */ bool save(); + private: File(const File &); File &operator=(const File &); diff --git a/taglib/mod/modfile.cpp b/taglib/mod/modfile.cpp index f242ea51..d25ecf27 100644 --- a/taglib/mod/modfile.cpp +++ b/taglib/mod/modfile.cpp @@ -70,6 +70,16 @@ Mod::Properties *Mod::File::audioProperties() const return &d->properties; } +TagDict Mod::File::toDict() const +{ + return d->tag.toDict(); +} + +void Mod::File::fromDict(const TagDict &tagDict) +{ + d->tag.fromDict(tagDict); +} + bool Mod::File::save() { if(readOnly()) { diff --git a/taglib/mod/modfile.h b/taglib/mod/modfile.h index f66a0ef3..7b1119c6 100644 --- a/taglib/mod/modfile.h +++ b/taglib/mod/modfile.h @@ -61,6 +61,17 @@ namespace TagLib { Mod::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * Forwards to Mod::Tag::toDict(). + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Forwards to Mod::Tag::fromDict(). + */ + void fromDict(const TagDict &); /*! * Returns the Mod::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/mod/modtag.cpp b/taglib/mod/modtag.cpp index 1dabe30e..89c21f09 100644 --- a/taglib/mod/modtag.cpp +++ b/taglib/mod/modtag.cpp @@ -20,7 +20,7 @@ ***************************************************************************/ #include "modtag.h" - +#include "tstringlist.h" using namespace TagLib; using namespace Mod; @@ -120,3 +120,31 @@ void Mod::Tag::setTrackerName(const String &trackerName) { d->trackerName = trackerName; } + +TagDict Mod::Tag::toDict() const +{ + TagDict dict; + dict["TITLE"] = d->title; + dict["COMMENT"] = d->comment; + if (!(d->trackerName == String::null)) + dict["TRACKERNAME"] = d->trackerName; + return dict; +} + +void Mod::Tag::fromDict(const TagDict &tagDict) +{ + if (tagDict.contains("TITLE") && !tagDict["TITILE"].isEmpty()) + d->title = tagDict["TITLE"][0]; + else + d->title = String::null; + + if (tagDict.contains("COMMENT") && !tagDict["COMMENT"].isEmpty()) + d->comment = tagDict["COMMENT"][0]; + else + d->comment = String::null; + + if (tagDict.contains("TRACKERNAME") && !tagDict["TRACKERNAME"].isEmpty()) + d->trackerName = tagDict["TRACKERNAME"][0]; + else + d->trackerName = String::null; +} diff --git a/taglib/mod/modtag.h b/taglib/mod/modtag.h index 253a4666..70f6bfc1 100644 --- a/taglib/mod/modtag.h +++ b/taglib/mod/modtag.h @@ -159,6 +159,21 @@ namespace TagLib { */ void setTrackerName(const String &trackerName); + /*! + * Implements the unified tag dictionary interface -- export function. + * Since the module tag is very limited, the exported dict is as well. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Because of the limitations of the module file tag, any tags besides + * COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be + * ignored. Additionally, if the dict contains tags with multiple values, + * all but the first will be ignored. + */ + void fromDict(const TagDict &); + private: Tag(const Tag &); Tag &operator=(const Tag &); diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index b868ada4..5a00b807 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "aifffile.h" @@ -83,6 +84,18 @@ ID3v2::Tag *RIFF::AIFF::File::tag() const return d->tag; } +TagLib::TagDict RIFF::AIFF::File::toDict(void) const +{ + return d->tag->toDict(); + +} + +void RIFF::AIFF::File::fromDict(const TagDict &dict) +{ + d->tag->fromDict(dict); +} + + RIFF::AIFF::Properties *RIFF::AIFF::File::audioProperties() const { return d->properties; diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index cac42934..af7d7b7a 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -83,6 +83,18 @@ namespace TagLib { */ virtual ID3v2::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * This method forwards to ID3v2::Tag::toDict. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * This method forwards to ID3v2::Tag::fromDict. + */ + void fromDict(const TagDict &); + /*! * 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 107d2b45..cb274dd1 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "wavfile.h" @@ -83,6 +84,17 @@ ID3v2::Tag *RIFF::WAV::File::tag() const return d->tag; } +TagLib::TagDict RIFF::WAV::File::toDict(void) const +{ + return d->tag->toDict(); +} + +void RIFF::WAV::File::fromDict(const TagDict &dict) +{ + d->tag->fromDict(dict); +} + + RIFF::WAV::Properties *RIFF::WAV::File::audioProperties() const { return d->properties; diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 341932b9..8e75afdb 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -83,6 +83,18 @@ namespace TagLib { */ virtual ID3v2::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * This method forwards to ID3v2::Tag::toDict. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * This method forwards to ID3v2::Tag::fromDict. + */ + void fromDict(const TagDict &); + /*! * Returns the WAV::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/s3m/s3mfile.cpp b/taglib/s3m/s3mfile.cpp index 0c8a712d..bf487aef 100644 --- a/taglib/s3m/s3mfile.cpp +++ b/taglib/s3m/s3mfile.cpp @@ -67,6 +67,16 @@ Mod::Tag *S3M::File::tag() const return &d->tag; } +TagDict S3M::File::toDict() const +{ + return d->tag.toDict(); +} + +void S3M::File::fromDict(const TagDict &tagDict) +{ + d->tag.fromDict(tagDict); +} + S3M::Properties *S3M::File::audioProperties() const { return &d->properties; diff --git a/taglib/s3m/s3mfile.h b/taglib/s3m/s3mfile.h index 6eb938a3..99c0402b 100644 --- a/taglib/s3m/s3mfile.h +++ b/taglib/s3m/s3mfile.h @@ -60,6 +60,18 @@ namespace TagLib { Mod::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * Forwards to Mod::Tag::toDict(). + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Forwards to Mod::Tag::fromDict(). + */ + void fromDict(const TagDict &); + /*! * Returns the S3M::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/tag.cpp b/taglib/tag.cpp index 9e0ea258..04d0c461 100644 --- a/taglib/tag.cpp +++ b/taglib/tag.cpp @@ -75,32 +75,32 @@ TagDict Tag::toDict() const void Tag::fromDict(const TagDict &dict) { - if (dict.contains("TITLE") and dict["TITLE"].size() >= 1) + if (dict.contains("TITLE") && dict["TITLE"].size() >= 1) setTitle(dict["TITLE"].front()); else setTitle(String::null); - if (dict.contains("ARTIST") and dict["ARTIST"].size() >= 1) + if (dict.contains("ARTIST") && !dict["ARTIST"].isEmpty()) setArtist(dict["ARTIST"].front()); else setArtist(String::null); - if (dict.contains("ALBUM") and dict["ALBUM"].size() >= 1) + if (dict.contains("ALBUM") && !dict["ALBUM"].isEmpty()) setAlbum(dict["ALBUM"].front()); else setAlbum(String::null); - if (dict.contains("COMMENT") and dict["COMMENT"].size() >= 1) + if (dict.contains("COMMENT") && !dict["COMMENT"].isEmpty()) setComment(dict["COMMENT"].front()); else setComment(String::null); - if (dict.contains("GENRE") and dict["GENRE"].size() >=1) + if (dict.contains("GENRE") && !dict["GENRE"].isEmpty()) setGenre(dict["GENRE"].front()); else setGenre(String::null); - if (dict.contains("DATE") and dict["DATE"].size() >= 1) { + if (dict.contains("DATE") && !dict["DATE"].isEmpty()) { bool ok; int date = dict["DATE"].front().toInt(&ok); if (ok) @@ -111,7 +111,7 @@ void Tag::fromDict(const TagDict &dict) else setYear(0); - if (dict.contains("TRACKNUMBER") and dict["TRACKNUMBER"].size() >= 1) { + if (dict.contains("TRACKNUMBER") && !dict["TRACKNUMBER"].isEmpty()) { bool ok; int track = dict["TRACKNUMBER"].front().toInt(&ok); if (ok) diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 9ac5f9bb..b08efbec 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -66,7 +66,7 @@ #include "modfile.h" #include "s3mfile.h" #include "itfile.h" -#include "xmfile.h" \ +#include "xmfile.h" using namespace TagLib; @@ -117,39 +117,76 @@ TagDict File::toDict() const { // ugly workaround until this method is virtual if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->toDict(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->toDict(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->toDict(); if (dynamic_cast(this)) return dynamic_cast(this)->toDict(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->toDict(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); + if (dynamic_cast(this)) + return dynamic_cast(this)->toDict(); // no specialized implementation available -> use generic one + // - ASF: ugly format, largely undocumented, not worth implementing + // dict interface ... + // - MP4: taglib's MP4::Tag does not really support anything beyond + // the basic implementation, therefor we use just the default Tag + // interface return tag()->toDict(); } void File::fromDict(const TagDict &dict) { - if (dynamic_cast(this)) - dynamic_cast< APE::File* >(this)->fromDict(dict); - else if (dynamic_cast(this)) - dynamic_cast< FLAC::File* >(this)->fromDict(dict); - else if (dynamic_cast(this)) - dynamic_cast< MPC::File* >(this)->fromDict(dict); - else if (dynamic_cast(this)) - dynamic_cast< MPEG::File* >(this)->fromDict(dict); - else if (dynamic_cast(this)) - dynamic_cast< Ogg::FLAC::File* >(this)->fromDict(dict); - else if (dynamic_cast(this)) - dynamic_cast< Ogg::Speex::File* >(this)->fromDict(dict); - else if (dynamic_cast(this)) - dynamic_cast< Ogg::Vorbis::File* >(this)->fromDict(dict); + if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); + else if (dynamic_cast(this)) + dynamic_cast(this)->fromDict(dict); else tag()->fromDict(dict); diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index b584b7fd..6875dda7 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "trueaudiofile.h" #include "id3v1tag.h" @@ -126,6 +127,27 @@ TagLib::Tag *TrueAudio::File::tag() const return &d->tag; } +TagLib::TagDict TrueAudio::File::toDict(void) const +{ + // once Tag::toDict() is virtual, this case distinction could actually be done + // within TagUnion. + if (d->hasID3v2) + return d->tag.access(ID3v2Index, false)->toDict(); + if (d->hasID3v1) + return d->tag.access(ID3v1Index, false)->toDict(); + return TagLib::TagDict(); +} + +void TrueAudio::File::fromDict(const TagDict &dict) +{ + if (d->hasID3v2) + d->tag.access(ID3v2Index, false)->fromDict(dict); + else if (d->hasID3v1) + d->tag.access(ID3v1Index, false)->fromDict(dict); + else + d->tag.access(ID3v2Index, true)->fromDict(dict); +} + TrueAudio::Properties *TrueAudio::File::audioProperties() const { return d->properties; diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index 9c866233..2489c7c4 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -124,6 +124,20 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * If the file contains both ID3v1 and v2 tags, only ID3v2 will be + * converted to the TagDict. + */ + TagDict toDict() const; + + /*! + * 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 &); + /*! * Returns the TrueAudio::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 19e4c77d..6afa188d 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -105,6 +105,25 @@ TagLib::Tag *WavPack::File::tag() const return &d->tag; } +TagLib::TagDict WavPack::File::toDict(void) const +{ + if (d->hasAPE) + return d->tag.access(APEIndex, false)->toDict(); + if (d->hasID3v1) + return d->tag.access(ID3v1Index, false)->toDict(); + return TagLib::TagDict(); +} + +void WavPack::File::fromDict(const TagDict &dict) +{ + if (d->hasAPE) + d->tag.access(APEIndex, false)->fromDict(dict); + else if (d->hasID3v1) + d->tag.access(ID3v1Index, false)->fromDict(dict); + else + d->tag.access(APE, true)->fromDict(dict); +} + WavPack::Properties *WavPack::File::audioProperties() const { return d->properties; diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index 5173c136..dcc57107 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -106,6 +106,20 @@ namespace TagLib { */ virtual TagLib::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * If the file contains both an APE and an ID3v1 tag, only APE + * will be converted to the TagDict. + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * As for the export, only one tag is taken into account. If the file + * has no tag at all, APE will be created. + */ + void fromDict(const TagDict &); + /*! * Returns the MPC::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/xm/xmfile.cpp b/taglib/xm/xmfile.cpp index 17aeab0a..bd450c85 100644 --- a/taglib/xm/xmfile.cpp +++ b/taglib/xm/xmfile.cpp @@ -379,6 +379,16 @@ Mod::Tag *XM::File::tag() const return &d->tag; } +TagDict XM::File::toDict() const +{ + return d->tag.toDict(); +} + +void XM::File::fromDict(const TagDict &tagDict) +{ + d->tag.fromDict(tagDict); +} + XM::Properties *XM::File::audioProperties() const { return &d->properties; diff --git a/taglib/xm/xmfile.h b/taglib/xm/xmfile.h index a4ae7244..27257fca 100644 --- a/taglib/xm/xmfile.h +++ b/taglib/xm/xmfile.h @@ -60,6 +60,18 @@ namespace TagLib { Mod::Tag *tag() const; + /*! + * Implements the unified tag dictionary interface -- export function. + * Forwards to Mod::Tag::toDict(). + */ + TagDict toDict() const; + + /*! + * Implements the unified tag dictionary interface -- import function. + * Forwards to Mod::Tag::fromDict(). + */ + void fromDict(const TagDict &); + /*! * Returns the XM::Properties for this file. If no audio properties * were read then this will return a null pointer. From c4cef551587e34b65163b19e525efa75089018b7 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 1 Jan 2012 14:42:48 +0100 Subject: [PATCH 09/39] Added tests and information about ignored id3 frames. The ID3v2::toDict() function now has an optional StringList* argument which will contain information about frames that could not be converted to the dict interface. There are some dict tests for APE and FLAC now, and the ID3v2 test was enlarged. --- taglib/ape/apetag.cpp | 1 + taglib/mpeg/id3v2/id3v2tag.cpp | 12 ++++++++---- taglib/mpeg/id3v2/id3v2tag.h | 7 ++++++- tests/test_apetag.cpp | 16 ++++++++++++++++ tests/test_flac.cpp | 22 ++++++++++++++++++++++ tests/test_id3v2.cpp | 6 ++++-- 6 files changed, 57 insertions(+), 7 deletions(-) diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 1f3f84a2..92d106c4 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -197,6 +197,7 @@ void APE::Tag::fromDict(const TagDict &orig_dict) { TagDict dict(orig_dict); // make a local copy that can be modified + // see comment in toDict() about TRACKNUMBER and YEAR if (dict.contains("TRACKNUMBER")) { dict.insert("TRACK", dict["TRACKNUMBER"]); dict.erase("TRACKNUMBER"); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 9c5e50f4..dd3e97a7 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -334,18 +334,22 @@ void ID3v2::Tag::removeFrames(const ByteVector &id) removeFrame(*it, true); } -TagDict ID3v2::Tag::toDict() const +TagDict ID3v2::Tag::toDict(StringList *ignoreInfo) const { TagDict dict; FrameList::ConstIterator frameIt = frameList().begin(); for (; frameIt != frameList().end(); ++frameIt) { ByteVector id = (*frameIt)->frameID(); - if (ignored(id)) + if (ignored(id)) { debug("toDict() found ignored id3 frame: " + id); - else if (deprecated(id)) + if (ignoreInfo) + ignoreInfo->append(String(id) + " frame is not supported"); + } else if (deprecated(id)) { debug("toDict() found deprecated id3 frame: " + id); - else { + 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); diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 3f731282..33d9a33c 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -264,8 +264,13 @@ namespace TagLib { * Implements the unified tag dictionary interface -- export function. * This function does some work to translate the hard-specified ID3v2 * frame types into a free-form string-to-stringlist dictionary. + * + * 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() const; + TagDict toDict(StringList *ignoredInfo = 0) const; /*! * Implements the unified tag dictionary interface -- import function. diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index 901a2aaf..fc38907f 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -15,6 +15,7 @@ class TestAPETag : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestAPETag); CPPUNIT_TEST(testIsEmpty); CPPUNIT_TEST(testIsEmpty2); + CPPUNIT_TEST(testDict); CPPUNIT_TEST_SUITE_END(); public: @@ -35,6 +36,21 @@ public: CPPUNIT_ASSERT(!tag.isEmpty()); } + void testDict() + { + APE::Tag tag; + TagDict dict = tag.toDict(); + CPPUNIT_ASSERT(dict.isEmpty()); + dict["ARTIST"] = String("artist 1"); + dict["ARTIST"].append("artist 2"); + dict["TRACKNUMBER"].append("17"); + tag.fromDict(dict); + CPPUNIT_ASSERT_EQUAL(String("17"), tag.itemListMap()["TRACK"].values()[0]); + CPPUNIT_ASSERT_EQUAL(2u, tag.itemListMap()["ARTIST"].values().size()); + CPPUNIT_ASSERT_EQUAL(String("artist 1"), tag.artist()); + CPPUNIT_ASSERT_EQUAL(17u, tag.track()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestAPETag); diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index 90baa844..f9a0afa4 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -22,6 +22,7 @@ class TestFLAC : public CppUnit::TestFixture CPPUNIT_TEST(testRemoveAllPictures); CPPUNIT_TEST(testRepeatedSave); CPPUNIT_TEST(testSaveMultipleValues); + CPPUNIT_TEST(testDict); CPPUNIT_TEST_SUITE_END(); public: @@ -208,6 +209,27 @@ public: CPPUNIT_ASSERT_EQUAL(String("artist 2"), m["ARTIST"][1]); } + void testDict() + { + // test unicode & multiple values with dict interface + ScopedFileCopy copy("silence-44-s", ".flac"); + string newname = copy.fileName(); + + FLAC::File *f = new FLAC::File(newname.c_str()); + TagDict dict; + dict["ARTIST"].append("artøst 1"); + dict["ARTIST"].append("artöst 2"); + f->fromDict(dict); + f->save(); + delete f; + + f = new FLAC::File(newname.c_str()); + dict = f->toDict(); + CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), dict["ARTIST"].size()); + CPPUNIT_ASSERT_EQUAL(String("artøst 1"), dict["ARTIST"][0]); + CPPUNIT_ASSERT_EQUAL(String("artöst 2"), dict["ARTIST"][1]); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 067bcd22..1823a5c3 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -553,20 +553,22 @@ public: ScopedFileCopy copy("rare_frames", ".mp3"); string newname = copy.fileName(); MPEG::File f(newname.c_str()); - TagDict dict = f.ID3v2Tag(false)->toDict(); + StringList ignored; + TagDict dict = f.ID3v2Tag(false)->toDict(&ignored); CPPUNIT_ASSERT_EQUAL(uint(6), dict.size()); CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]); CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]); CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION2"][0]); CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION2"][1]); - CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"][0]); CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"][0]); CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["USERURL"][0]); CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"][0]); CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"][0]); + CPPUNIT_ASSERT_EQUAL(String("UFID frame is not supported"), ignored[0]); + CPPUNIT_ASSERT_EQUAL(1u, ignored.size()); } }; From 67d896e6a7405b8c3d88c0e813c4476116cec057 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 14 Jan 2012 22:02:17 +0100 Subject: [PATCH 10/39] Implemented the most easy comments on the pull request. --- CMakeLists.txt | 4 ++-- taglib/ape/apetag.cpp | 6 ++++-- taglib/it/itfile.h | 4 ++-- taglib/mpeg/id3v2/id3v2dicttools.cpp | 13 +++++++------ 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c0c9097..d6976634 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,8 @@ if(VISIBILITY_HIDDEN) add_definitions (-fvisibility=hidden) endif() -option(BUILD_TESTS "Build the test suite" OFF) -option(BUILD_EXAMPLES "Build the examples" OFF) +option(BUILD_TESTS "Build the test suite" ON) +option(BUILD_EXAMPLES "Build the examples" ON) option(NO_ITUNES_HACKS "Disable workarounds for iTunes bugs" OFF) diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 92d106c4..e1bf40c2 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -187,15 +187,17 @@ TagDict APE::Tag::toDict() const tagName = "TRACKNUMBER"; else if (tagName == "YEAR") tagName = "DATE"; + else if (tagName == "ALBUM ARTIST") + tagName = "ALBUMARTIST"; if (it->second.type() == Item::Text) dict[tagName].append(it->second.toStringList()); } return dict; } -void APE::Tag::fromDict(const TagDict &orig_dict) +void APE::Tag::fromDict(const TagDict &origDict) { - TagDict dict(orig_dict); // make a local copy that can be modified + TagDict dict(origDict); // make a local copy that can be modified // see comment in toDict() about TRACKNUMBER and YEAR if (dict.contains("TRACKNUMBER")) { diff --git a/taglib/it/itfile.h b/taglib/it/itfile.h index e4a744e9..81b6c157 100644 --- a/taglib/it/itfile.h +++ b/taglib/it/itfile.h @@ -61,14 +61,14 @@ namespace TagLib { Mod::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. * Forwards to Mod::Tag::toDict(). + * BIC: will be removed once File::toDict() is made virtual */ TagDict toDict() const; /*! - * Implements the unified tag dictionary interface -- import function. * Forwards to Mod::Tag::fromDict(). + * BIC: will be removed once File::fromDict() is made virtual */ void fromDict(const TagDict &); diff --git a/taglib/mpeg/id3v2/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp index 498f92ed..7ee6c83d 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.cpp +++ b/taglib/mpeg/id3v2/id3v2dicttools.cpp @@ -57,7 +57,7 @@ namespace TagLib { /*! * A map of translations frameID <-> tag used by the unified dictionary interface. */ - static const uint numid3frames = 54; + static const uint numid3frames = 55; static const char *id3frames[][2] = { // Text information frames { "TALB", "ALBUM"}, @@ -67,14 +67,14 @@ namespace TagLib { { "TCOP", "COPYRIGHT" }, { "TDEN", "ENCODINGTIME" }, { "TDLY", "PLAYLISTDELAY" }, - { "TDOR", "ORIGINALRELEASETIME" }, + { "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", "RELEASETIME" }, - { "TDTG", "TAGGINGTIME" }, + { "TDRL", "RELEASEDATE" }, + { "TDTG", "TAGGINGDATE" }, { "TENC", "ENCODEDBY" }, { "TEXT", "LYRICIST" }, { "TFLT", "FILETYPE" }, @@ -94,9 +94,9 @@ namespace TagLib { { "TOPE", "ORIGINALARTIST" }, { "TOWN", "OWNER" }, { "TPE1", "ARTIST"}, - { "TPE2", "PERFORMER" }, + { "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST' { "TPE3", "CONDUCTOR" }, - { "TPE4", "ARRANGER" }, + { "TPE4", "REMIXER" }, // could also be ARRANGER { "TPOS", "DISCNUMBER" }, { "TPRO", "PRODUCEDNOTICE" }, { "TPUB", "PUBLISHER" }, @@ -106,6 +106,7 @@ namespace TagLib { { "TSOA", "ALBUMSORT" }, { "TSOP", "ARTISTSORT" }, { "TSOT", "TITLESORT" }, + { "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes { "TSRC", "ISRC" }, { "TSSE", "ENCODING" }, From d11189b975d4062898881d3e4f163bacfb9ae5dc Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Mon, 16 Jan 2012 22:37:30 +0100 Subject: [PATCH 11/39] Basic implementation of a PropertyMap. Implemented key/valuelist property map with case-insensitive ASCII keys and StringList values. Todo: - subclass StringList to add flags indicating whether a value could be written to the specific file format - add member attribute indicating list of frames that could not be parsed into the PropertyMap representation. --- taglib/CMakeLists.txt | 2 + taglib/mpeg/id3v2/id3v2dicttools.cpp | 27 ++++++ taglib/toolkit/tpropertymap.cpp | 120 +++++++++++++++++++++++++++ taglib/toolkit/tpropertymap.h | 118 ++++++++++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 taglib/toolkit/tpropertymap.cpp create mode 100644 taglib/toolkit/tpropertymap.h diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index ec8a5d81..a7777a5b 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -48,6 +48,7 @@ set(tag_HDRS toolkit/tfilestream.h toolkit/tmap.h toolkit/tmap.tcc + toolkit/tpropertymap.h mpeg/mpegfile.h mpeg/mpegproperties.h mpeg/mpegheader.h @@ -277,6 +278,7 @@ set(toolkit_SRCS toolkit/tfile.cpp toolkit/tfilestream.cpp toolkit/tdebug.cpp + toolkit/tpropertymap.cpp toolkit/unicode.cpp ) diff --git a/taglib/mpeg/id3v2/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp index 7ee6c83d..63a0398b 100644 --- a/taglib/mpeg/id3v2/id3v2dicttools.cpp +++ b/taglib/mpeg/id3v2/id3v2dicttools.cpp @@ -134,6 +134,33 @@ namespace TagLib { return m; } + // list of TXXX frame description conversions + static const uint txxxConversionSize = 4; + static const char *txxxConversionFrames[][2] = { + {"MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID"}, + {"MusicBrainz Disc Id", "MUSICBRAINZ_DISCID"}, + {"MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID"}, + {"MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID"}, + {"MusicMagic Fingerprint", "MUSICIP_FINGERPRINT"}, + {"MusicIP PUID", "MUSICIP_PUID"}, + {"MusicBrainz Album Release Country", "RELEASECOUNTRY"}, + {"MusicBrainz Album Status", "MUSICBRAINZ_ALBUMSTATUS"}, + {"MusicBrainz Album Type", "MUSICBRAINZ_ALBUMTYPE"}, + {"MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASE_GROUPID"}, + {"MusicBrainz Work Id", "MUSICBRAINZ_WORKID"}, + {"MusicBrainz Original Album Id", "MUSICBRAINZ_ORIGINALALBUMID"}, + {"Acoustid Fingerprint", "ACOUSTID_FINGERPRINT"}, + {"Acoustid Id", "ACOUSTID_ID"} + }; + + FrameIDMap &txxxConversionMap() + { + static FrameIDMap txxxMap; + if (txxxMap.isEmpty()) + for(uint i = 0; i < txxxConversionSize; ++i) + txxxMap[txxxConversionFrames[i][0]] = txxxConversionFrames[i][1]; + return txxxMap; + } String frameIDToTagName(const ByteVector &id) { Map &m = idMap(); diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp new file mode 100644 index 00000000..08b94ba7 --- /dev/null +++ b/taglib/toolkit/tpropertymap.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + copyright : (C) 2012 by Michael Helmling + email : helmling@mathematik.uni-kl.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#include "tpropertymap.h" + +using namespace TagLib; + +typedef Map supertype; + +PropertyMap::PropertyMap() : Map() +{ +} + +PropertyMap::PropertyMap(const PropertyMap &m) : Map(m) +{ +} + +PropertyMap::~PropertyMap() +{ +} + +bool PropertyMap::insert(const String &key, const StringList &values) +{ + String realKey = prepareKey(key); + if (realKey.isNull()) + return false; + + supertype::operator[](realKey).append(values); + return true; +} + +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); + return true; +} + +PropertyMap::Iterator PropertyMap::find(const String &key) +{ + String realKey = prepareKey(key); + if (realKey.isNull()) + return end(); + return supertype::find(realKey); +} + +PropertyMap::ConstIterator PropertyMap::find(const String &key) const +{ + String realKey = prepareKey(key); + if (realKey.isNull()) + return end(); + return supertype::find(realKey); +} + +bool PropertyMap::contains(const String &key) const +{ + String realKey = prepareKey(key); + if (realKey.isNull()) + return false; + return supertype::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()) + return *this; + supertype::erase(realKey); + return *this; +} + +const StringList &PropertyMap::operator[](const String &key) const +{ + String realKey = prepareKey(key); + return supertype::operator[](realKey); +} + +StringList &PropertyMap::operator[](const String &key) +{ + String realKey = prepareKey(key); + if (realKey.isNull()) + return supertype::operator[](realKey); // invalid case + if (!supertype::contains(realKey)) + supertype::insert(realKey, StringList()); + return supertype::operator[](realKey); +} + +String PropertyMap::prepareKey(const String &proposed) const { + if (proposed.isEmpty()) + return String::null; + for (String::ConstIterator it = proposed.begin(); it != proposed.end(); it++) + // forbid non-printable, non-ascii, '=' (#61) and '~' (#126) + if (*it < 32 || *it >= 128 || *it == 61 || *it == 126) + return String::null; + return proposed.upper(); +} diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h new file mode 100644 index 00000000..f82a7dfb --- /dev/null +++ b/taglib/toolkit/tpropertymap.h @@ -0,0 +1,118 @@ +/*************************************************************************** + copyright : (C) 2012 by Michael Helmling + email : helmling@mathematik.uni-kl.de + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef PROPERTYMAP_H_ +#define PROPERTYMAP_H_ + +#include "tmap.h" +#include "tstringlist.h" + +namespace TagLib { + + + //! A map for format-independent tag representations. + + /*! + * 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. + * 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. + * + */ + + class PropertyMap: public Map + { + public: + + typedef Map::Iterator Iterator; + typedef Map::ConstIterator ConstIterator; + + PropertyMap(); + + PropertyMap(const PropertyMap &m); + + virtual ~PropertyMap(); + + /*! + * Inserts \a values under \a key in the map. If \a key already exists, + * then \values will be appended to the existing StringList. + * The returned value indicates success, i.e. whether \a key is a + * valid key. + */ + bool insert(const String &key, const StringList &values); + + /*! + * Replaces any existing values for \a key with the given \a values, + * and simply insert them if \a key did not exist before. + * The returned value indicates success, i.e. whether \a key is a + * valid key. + */ + bool replace(const String &key, const StringList &values); + + /*! + * Find the first occurrence of \a key. + */ + Iterator find(const String &key); + + /*! + * Find the first occurrence of \a key. + */ + ConstIterator find(const String &key) const; + + /*! + * Returns true if the map contains values for \a key. + */ + bool contains(const String &key) const; + + /*! + * Erase the \a key and its values from the map. + */ + PropertyMap &erase(const String &key); + + /*! + * Returns a reference to the value associated with \a key. + * + * \note: This has undefined behavior if the key is not valid. + */ + const StringList &operator[](const String &key) const; + + /*! + * Returns a reference to the value associated with \a key. + * + * If \a key is not present in the map, an empty list is inserted and + * returned. + * + * \note: This has undefined behavior if the key is not valid. + */ + StringList &operator[](const String &key); + + /*! + * Converts \a proposed into another String suitable to be used as + * a key, or returns String::null if this is not possible. + */ + String prepareKey(const String &proposed) const; + }; + +} +#endif /* PROPERTYMAP_H_ */ From 18ae797df474f1703d36f6ba6a4497b98550dee0 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Tue, 17 Jan 2012 18:09:30 +0100 Subject: [PATCH 12/39] Add unsupportedData() to PropertyMap, simplified [] behavior. --- taglib/toolkit/tpropertymap.cpp | 15 ++++++++++----- taglib/toolkit/tpropertymap.h | 25 +++++++++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 08b94ba7..00aeca88 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -43,7 +43,11 @@ bool PropertyMap::insert(const String &key, const StringList &values) if (realKey.isNull()) return false; - supertype::operator[](realKey).append(values); + Iterator result = supertype::find(realKey); + if (result == end()) + supertype::insert(realKey, values); + else + supertype::operator[](realKey).append(values); return true; } @@ -102,13 +106,14 @@ const StringList &PropertyMap::operator[](const String &key) const StringList &PropertyMap::operator[](const String &key) { String realKey = prepareKey(key); - if (realKey.isNull()) - return supertype::operator[](realKey); // invalid case - if (!supertype::contains(realKey)) - supertype::insert(realKey, StringList()); return supertype::operator[](realKey); } +StringList &PropertyMap::unsupportedData() +{ + return unsupported; +} + String PropertyMap::prepareKey(const String &proposed) const { if (proposed.isEmpty()) return String::null; diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index f82a7dfb..2025873d 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -41,7 +41,7 @@ namespace TagLib { * */ - class PropertyMap: public Map + class TAGLIB_EXPORT PropertyMap: public Map { public: @@ -93,25 +93,38 @@ namespace TagLib { /*! * Returns a reference to the value associated with \a key. * - * \note: This has undefined behavior if the key is not valid. + * \note: This has undefined behavior if the key is not valid or not + * present in the map. */ const StringList &operator[](const String &key) const; /*! * Returns a reference to the value associated with \a key. * - * If \a key is not present in the map, an empty list is inserted and - * returned. - * - * \note: This has undefined behavior if the key is not valid. + * \note: This has undefined behavior if the key is not valid or not + * present in the map. */ StringList &operator[](const String &key); + /*! + * If a PropertyMap is read from a File object using File::properties(), + * the StringList returned from this function will represent metadata + * that could not be parsed into the PropertyMap representation. This could + * be e.g. binary data, unknown ID3 frames, etc. + * You can remove items from the returned list, which tells TagLib to remove + * those unsupported elements if you call File::setProperties() with the + * same PropertyMap as argument. + */ + StringList &unsupportedData(); + + private: + /*! * Converts \a proposed into another String suitable to be used as * a key, or returns String::null if this is not possible. */ String prepareKey(const String &proposed) const; + StringList unsupported; }; } From e4d955d6ef703be049f47df278b30e567ab27924 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 21 Jan 2012 14:52:24 +0100 Subject: [PATCH 13/39] Migration to new PropertyMap ... done ape to mod. --- taglib/ape/apefile.cpp | 32 ++++++---- taglib/ape/apefile.h | 16 +++-- taglib/ape/apetag.cpp | 87 ++++++++++++++----------- taglib/ape/apetag.h | 17 +++-- taglib/flac/flacfile.cpp | 44 ++++++++----- taglib/flac/flacfile.h | 12 ++-- taglib/it/itfile.cpp | 8 +-- taglib/it/itfile.h | 10 +-- taglib/mod/modfile.cpp | 8 +-- taglib/mod/modfile.h | 12 ++-- taglib/mod/modtag.cpp | 50 ++++++++++----- taglib/mod/modtag.h | 15 +++-- taglib/tag.cpp | 110 ++++++++++++++++++++------------ taglib/tag.h | 40 ++++++------ taglib/toolkit/tfile.cpp | 103 ++++++++++++++++++++---------- taglib/toolkit/tfile.h | 25 ++++++-- taglib/toolkit/tpropertymap.cpp | 27 +++++--- taglib/toolkit/tpropertymap.h | 16 ++++- 18 files changed, 398 insertions(+), 234 deletions(-) diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 7c63412e..6e806415 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -109,23 +109,31 @@ TagLib::Tag *APE::File::tag() const return &d->tag; } -TagLib::TagDict APE::File::toDict(void) const +PropertyMap APE::File::properties() const { - 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 APE::File::fromDict(const TagDict &dict) +void APE::File::removeUnsupportedProperties(const StringList &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) + d->tag.access(APEIndex, false)->removeUnsupportedProperties(properties); + if(d->hasID3v1) + d->tag.access(ID3v1Index, false)->removeUnsupportedProperties(properties); +} + +PropertyMap APE::File::setProperties(const PropertyMap &properties) +{ + 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(APE, true)->fromDict(dict); + return d->tag.access(APE, true)->setProperties(properties); } APE::Properties *APE::File::audioProperties() const diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index ab290b83..0bdbd422 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -111,18 +111,24 @@ 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 APE - * will be converted to the TagDict. + * will be converted to the PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. + * Removes unsupported properties. Forwards to the actual Tag's + * removeUnsupportedProperties() function. + */ + void removeUnsupportedProperties(const StringList &properties); + + /*! + * Implements the unified property interface -- import function. * As for the export, only one tag is taken into account. If the file * has no tag at all, APE will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the APE::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index e1bf40c2..5393d72d 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -174,61 +174,73 @@ void APE::Tag::setTrack(uint i) addValue("TRACK", String::number(i), true); } -TagDict APE::Tag::toDict() const +// conversions of tag keys between what we use in PropertyMap and what's usual +// for APE tags +static const uint keyConversionsSize = 5; //usual, APE +static const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" }, + {"DATE", "YEAR" }, + {"ALBUMARTIST", "ALBUM ARTIST"}, + {"DISCNUMBER", "DISC" }, + {"REMIXER", "MIXARTIST" }}; + +PropertyMap APE::Tag::properties() const { - TagDict dict; + PropertyMap properties; ItemListMap::ConstIterator it = itemListMap().begin(); - for (; it != itemListMap().end(); ++it) { - String tagName = it->first.upper(); - // These two tags need to be handled specially; in APE tags the track number is usually - // named TRACK instead of TRACKNUMBER, the date tag is YEAR instead of DATE - // - if (tagName == "TRACK") - tagName = "TRACKNUMBER"; - else if (tagName == "YEAR") - tagName = "DATE"; - else if (tagName == "ALBUM ARTIST") - tagName = "ALBUMARTIST"; - if (it->second.type() == Item::Text) - dict[tagName].append(it->second.toStringList()); + for(; it != itemListMap().end(); ++it) { + String tagName = PropertyMap::prepareKey(it->first); + // if the item is Binary or Locator, or if the key is an invalid string, + // add to unsupportedData + if(it->second.type() != Item::Text || tagName.isNull()) + properties.unsupportedData().append(it->first); + else { + // Some tags need to be handled specially + for(uint i = 0; i < keyConversionsSize; ++i) + if(tagName == keyConversions[i][1]) + tagName = keyConversions[i][0]; + properties[tagName].append(it->second.toStringList()); + } } - return dict; + return properties; } -void APE::Tag::fromDict(const TagDict &origDict) +void APE::Tag::removeUnsupportedProperties(const StringList &properties) { - TagDict dict(origDict); // make a local copy that can be modified + StringList::ConstIterator it = properties.begin(); + for(; it != properties.end(); ++it) + removeItem(*it); +} - // see comment in toDict() about TRACKNUMBER and YEAR - if (dict.contains("TRACKNUMBER")) { - dict.insert("TRACK", dict["TRACKNUMBER"]); - dict.erase("TRACKNUMBER"); - } - if (dict.contains("DATE")) { - dict.insert("YEAR", dict["DATE"]); - dict.erase("DATE"); - } +PropertyMap APE::Tag::setProperties(const PropertyMap &origProps) +{ + PropertyMap properties(origProps); // make a local copy that can be modified + + // see comment in properties() + for(uint i = 0; i < keyConversionsSize; ++i) + if(properties.contains(keyConversions[i][0])) { + properties.insert(keyConversions[i][1], properties[keyConversions[i][0]]); + properties.erase(keyConversions[i][0]); + } // first check if tags need to be removed completely StringList toRemove; ItemListMap::ConstIterator remIt = itemListMap().begin(); - for (; remIt != itemListMap().end(); ++remIt) { - if (remIt->second.type() != APE::Item::Text) - // ignore binary and locator APE items - continue; - if (!dict.contains(remIt->first.upper())) + for(; remIt != itemListMap().end(); ++remIt) { + String key = PropertyMap::prepareKey(remIt->first); + // only remove if a) key is valid, b) type is text, c) key not contained in new properties + if(!key.isNull() && remIt->second.type() == APE::Item::Text && !properties.contains(key)) toRemove.append(remIt->first); } for (StringList::Iterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++) removeItem(*removeIt); - // now sync in the "forward direction - TagDict::ConstIterator it = dict.begin(); - for (; it != dict.end(); ++it) { + // now sync in the "forward direction" + PropertyMap::ConstIterator it = properties.begin(); + for(; it != properties.end(); ++it) { const String &tagName = it->first; - if (!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) { - if (it->second.size() == 0) + if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) { + if(it->second.size() == 0) removeItem(tagName); else { StringList::ConstIterator valueIt = it->second.begin(); @@ -239,6 +251,7 @@ void APE::Tag::fromDict(const TagDict &origDict) } } } + return PropertyMap; } APE::Footer *APE::Tag::footer() const diff --git a/taglib/ape/apetag.h b/taglib/ape/apetag.h index 089420ea..8520609e 100644 --- a/taglib/ape/apetag.h +++ b/taglib/ape/apetag.h @@ -107,20 +107,25 @@ namespace TagLib { * Implements the unified tag dictionary interface -- export function. * APE tags are perfectly compatible with the dictionary interface because they * support both arbitrary tag names and multiple values. Currently only - * APE items of type *Text* are handled by the dictionary interface, while - * *Binary* and *Locator* items are simply ignored. + * APE items of type *Text* are handled by the dictionary interface; all *Binary* + * and *Locator* items will be put into the unsupportedData list and can be + * deleted on request using removeUnsupportedProperties(). The same happens + * to Text items if their key is invalid for PropertyMap (which should actually + * never happen). * * The only conversion done by this export function is to rename the APE tags - * TRACK to TRACKNUMBER and YEAR to DATE, respectively, in order to be compliant - * with the names used in other formats. + * TRACK to TRACKNUMBER, YEAR to DATE, and ALBUM ARTIST to ALBUMARTIST, respectively, + * in order to be compliant with the names used in other formats. */ - TagDict toDict() const; + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &properties); /*! * Implements the unified tag dictionary interface -- import function. The same * comments as for the export function apply. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns a pointer to the tag's footer. diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index ec925d0f..352ee27e 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -138,29 +138,39 @@ TagLib::Tag *FLAC::File::tag() const return &d->tag; } -TagLib::TagDict FLAC::File::toDict(void) const +PropertyMap FLAC::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->hasXiphComment) - return d->tag.access(XiphIndex, false)->toDict(); - if (d->hasID3v2) - return d->tag.access(ID3v2Index, false)->toDict(); - if (d->hasID3v1) - return d->tag.access(ID3v1Index, false)->toDict(); - return TagLib::TagDict(); + if(d->hasXiphComment) + return d->tag.access(XiphIndex, false)->properties(); + if(d->hasID3v2) + return d->tag.access(ID3v2Index, false)->properties(); + if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->properties(); + return PropertyMap(); } -void FLAC::File::fromDict(const TagDict &dict) +void FLAC::File::removeUnsupportedProperties(const StringList &unsupported) { - if (d->hasXiphComment) - d->tag.access(XiphIndex, false)->fromDict(dict); - else if (d->hasID3v2) - d->tag.access(ID3v2Index, false)->fromDict(dict); - else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); + if(d->hasXiphComment) + d->tag.access(XiphIndex, false)->removeUnsupportedProperties(unsupported); + if(d->hasID3v2) + d->tag.access(ID3v2Index, false)->removeUnsupportedProperties(unsupported); + if(d->hasID3v1) + d->tag.access(ID3v1Index, false)->removeUnsupportedProperties(unsupported); +} + +PropertyMap FLAC::File::setProperties(const PropertyMap &properties) +{ + if(d->hasXiphComment) + return d->tag.access(XiphIndex, false)->setProperties(properties); + else if(d->hasID3v2) + return d->tag.access(ID3v2Index, false)->setProperties(properties); + else if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->setProperties(properties); else - d->tag.access(XiphIndex, true)->fromDict(dict); + return d->tag.access(XiphIndex, true)->setProperties(properties); } FLAC::Properties *FLAC::File::audioProperties() const diff --git a/taglib/flac/flacfile.h b/taglib/flac/flacfile.h index 9fdc1c2e..b2ecce22 100644 --- a/taglib/flac/flacfile.h +++ b/taglib/flac/flacfile.h @@ -119,19 +119,21 @@ 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 more than one tag (e.g. XiphComment and ID3v1), * only the first one (in the order XiphComment, ID3v2, ID3v1) will be - * converted to the TagDict. + * converted to the PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; + + void removeUnsupportedProperties(const StringList &); /*! - * 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, a XiphComment will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the FLAC::Properties for this file. If no audio properties diff --git a/taglib/it/itfile.cpp b/taglib/it/itfile.cpp index 5815b98a..1c3474cb 100644 --- a/taglib/it/itfile.cpp +++ b/taglib/it/itfile.cpp @@ -65,14 +65,14 @@ Mod::Tag *IT::File::tag() const return &d->tag; } -TagDict IT::File::toDict() const +PropertyMap IT::File::properties() const { - return d->tag.toDict(); + return d->tag.properties(); } -void IT::File::fromDict(const TagDict &tagDict) +PropertyMap IT::File::setProperties(const PropertyMap &properties) { - d->tag.fromDict(tagDict); + return d->tag.setProperties(properties); } IT::Properties *IT::File::audioProperties() const diff --git a/taglib/it/itfile.h b/taglib/it/itfile.h index 81b6c157..9c507742 100644 --- a/taglib/it/itfile.h +++ b/taglib/it/itfile.h @@ -61,16 +61,16 @@ namespace TagLib { Mod::Tag *tag() const; /*! - * Forwards to Mod::Tag::toDict(). + * Forwards to Mod::Tag::properties(). * BIC: will be removed once File::toDict() is made virtual */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Forwards to Mod::Tag::fromDict(). - * BIC: will be removed once File::fromDict() is made virtual + * Forwards to Mod::Tag::setProperties(). + * BIC: will be removed once File::setProperties() is made virtual */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the IT::Properties for this file. If no audio properties diff --git a/taglib/mod/modfile.cpp b/taglib/mod/modfile.cpp index d25ecf27..a05c2137 100644 --- a/taglib/mod/modfile.cpp +++ b/taglib/mod/modfile.cpp @@ -70,14 +70,14 @@ Mod::Properties *Mod::File::audioProperties() const return &d->properties; } -TagDict Mod::File::toDict() const +PropertyMap Mod::File::properties() const { - return d->tag.toDict(); + return d->tag.properties(); } -void Mod::File::fromDict(const TagDict &tagDict) +PropertyMap Mod::File::setProperties(const PropertyMap &properties) { - d->tag.fromDict(tagDict); + return d->tag.setProperties(properties); } bool Mod::File::save() diff --git a/taglib/mod/modfile.h b/taglib/mod/modfile.h index 7b1119c6..9e79659c 100644 --- a/taglib/mod/modfile.h +++ b/taglib/mod/modfile.h @@ -62,16 +62,16 @@ namespace TagLib { Mod::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. - * Forwards to Mod::Tag::toDict(). + * Implements the unified property interface -- export function. + * Forwards to Mod::Tag::properties(). */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * Forwards to Mod::Tag::fromDict(). + * Implements the unified property interface -- import function. + * Forwards to Mod::Tag::setProperties(). */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the Mod::Properties for this file. If no audio properties * were read then this will return a null pointer. diff --git a/taglib/mod/modtag.cpp b/taglib/mod/modtag.cpp index 89c21f09..aaa2f16a 100644 --- a/taglib/mod/modtag.cpp +++ b/taglib/mod/modtag.cpp @@ -121,30 +121,46 @@ void Mod::Tag::setTrackerName(const String &trackerName) d->trackerName = trackerName; } -TagDict Mod::Tag::toDict() const +PropertyMap Mod::Tag::properties() const { - TagDict dict; - dict["TITLE"] = d->title; - dict["COMMENT"] = d->comment; - if (!(d->trackerName == String::null)) - dict["TRACKERNAME"] = d->trackerName; - return dict; + PropertyMap properties; + properties["TITLE"] = d->title; + properties["COMMENT"] = d->comment; + if(!(d->trackerName.isNull())) + properties["TRACKERNAME"] = d->trackerName; + return properties; } -void Mod::Tag::fromDict(const TagDict &tagDict) +PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps) { - if (tagDict.contains("TITLE") && !tagDict["TITILE"].isEmpty()) - d->title = tagDict["TITLE"][0]; - else + PropertyMap properties(origProps); + properties.removeEmpty(); + StringList oneValueSet; + if(properties.contains("TITLE")) { + d->title = properties["TITLE"].front(); + oneValueSet.append("TITLE"); + } else d->title = String::null; - if (tagDict.contains("COMMENT") && !tagDict["COMMENT"].isEmpty()) - d->comment = tagDict["COMMENT"][0]; - else + if(properties.contains("COMMENT")) { + d->comment = properties["COMMENT"].front(); + oneValueSet.append("COMMENT"); + } else d->comment = String::null; - if (tagDict.contains("TRACKERNAME") && !tagDict["TRACKERNAME"].isEmpty()) - d->trackerName = tagDict["TRACKERNAME"][0]; - else + if(properties.contains("TRACKERNAME")) { + d->trackerName = properties["TRACKERNAME"].front(); + oneValueSet.append("TRACKERNAME"); + } else d->trackerName = String::null; + + // for each tag that has been set above, remove the first entry in the corresponding + // value list. The others will be returned as unsupported by this format. + for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { + if(properties[*it].size() == 1) + properties.erase(*it); + else + properties[*it].erase( properties[*it].begin() ); + } + return properties; } diff --git a/taglib/mod/modtag.h b/taglib/mod/modtag.h index 70f6bfc1..c1a74b10 100644 --- a/taglib/mod/modtag.h +++ b/taglib/mod/modtag.h @@ -160,19 +160,20 @@ namespace TagLib { void setTrackerName(const String &trackerName); /*! - * Implements the unified tag dictionary interface -- export function. - * Since the module tag is very limited, the exported dict is as well. + * Implements the unified property interface -- export function. + * Since the module tag is very limited, the exported map is as well. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. + * Implements the unified property interface -- import function. * Because of the limitations of the module file tag, any tags besides * COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be - * ignored. Additionally, if the dict contains tags with multiple values, - * all but the first will be ignored. + * returened. Additionally, if the map contains tags with multiple values, + * all but the first will be contained in the returned map of unsupported + * properties. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); private: Tag(const Tag &); diff --git a/taglib/tag.cpp b/taglib/tag.cpp index 04d0c461..3cebd134 100644 --- a/taglib/tag.cpp +++ b/taglib/tag.cpp @@ -53,75 +53,101 @@ bool Tag::isEmpty() const track() == 0); } -TagDict Tag::toDict() const +PropertyMap Tag::properties() const { - TagDict dict; - if (!(title() == String::null)) - dict["TITLE"].append(title()); - if (!(artist() == String::null)) - dict["ARTIST"].append(artist()); - if (!(album() == String::null)) - dict["ALBUM"].append(album()); - if (!(comment() == String::null)) - dict["COMMENT"].append(comment()); - if (!(genre() == String::null)) - dict["GENRE"].append(genre()); - if (!(year() == 0)) - dict["DATE"].append(String::number(year())); - if (!(track() == 0)) - dict["TRACKNUMBER"].append(String::number(track())); - return dict; + PropertyMap map; + if(!(title().isNull())) + map["TITLE"].append(title()); + if(!(artist().isNull())) + map["ARTIST"].append(artist()); + if(!(album().isNull())) + map["ALBUM"].append(album()); + if(!(comment().isNull())) + map["COMMENT"].append(comment()); + if(!(genre().isNull())) + map["GENRE"].append(genre()); + if(!(year() == 0)) + map["DATE"].append(String::number(year())); + if(!(track() == 0)) + map["TRACKNUMBER"].append(String::number(track())); + return map; } -void Tag::fromDict(const TagDict &dict) +void Tag::removeUnsupportedProperties(const StringList&) { - if (dict.contains("TITLE") && dict["TITLE"].size() >= 1) - setTitle(dict["TITLE"].front()); - else +} + +PropertyMap Tag::setProperties(const PropertyMap &origProps) +{ + PropertyMap properties(origProps); + properties.removeEmpty(); + StringList oneValueSet; + // can this be simplified by using some preprocessor defines / function pointers? + if(properties.contains("TITLE")) { + setTitle(properties["TITLE"].front()); + oneValueSet.append("TITLE"); + } else setTitle(String::null); - if (dict.contains("ARTIST") && !dict["ARTIST"].isEmpty()) - setArtist(dict["ARTIST"].front()); - else + if(properties.contains("ARTIST")) { + setArtist(properties["ARTIST"].front()); + oneValueSet.append("ARTIST"); + } else setArtist(String::null); - if (dict.contains("ALBUM") && !dict["ALBUM"].isEmpty()) - setAlbum(dict["ALBUM"].front()); - else - setAlbum(String::null); + if(properties.contains("ALBUM")) { + setAlbum(properties["ALBUM"].front()); + oneValueSet.append("ALBUM"); + } else + setAlbum(String::null); - if (dict.contains("COMMENT") && !dict["COMMENT"].isEmpty()) - setComment(dict["COMMENT"].front()); - else + if(properties.contains("COMMENT")) { + setComment(properties["COMMENT"].front()); + oneValueSet.append("COMMENT"); + } else setComment(String::null); - if (dict.contains("GENRE") && !dict["GENRE"].isEmpty()) - setGenre(dict["GENRE"].front()); - else + if(properties.contains("GENRE")) { + setGenre(properties["GENRE"].front()); + oneValueSet.append("GENRE"); + } else setGenre(String::null); - if (dict.contains("DATE") && !dict["DATE"].isEmpty()) { + if(properties.contains("DATE")) { bool ok; - int date = dict["DATE"].front().toInt(&ok); - if (ok) + int date = properties["DATE"].front().toInt(&ok); + if(ok) { setYear(date); - else + oneValueSet.append("DATE"); + } else setYear(0); } else setYear(0); - if (dict.contains("TRACKNUMBER") && !dict["TRACKNUMBER"].isEmpty()) { + if(properties.contains("TRACKNUMBER")) { bool ok; - int track = dict["TRACKNUMBER"].front().toInt(&ok); - if (ok) + int track = properties["TRACKNUMBER"].front().toInt(&ok); + if(ok) { setTrack(track); - else + oneValueSet.append("TRACKNUMBER"); + } else setTrack(0); } else setYear(0); + + // for each tag that has been set above, remove the first entry in the corresponding + // value list. The others will be returned as unsupported by this format. + for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) { + if(properties[*it].size() == 1) + properties.erase(*it); + else + properties[*it].erase( properties[*it].begin() ); + } + return properties; } + void Tag::duplicate(const Tag *source, Tag *target, bool overwrite) // static { if(overwrite) { diff --git a/taglib/tag.h b/taglib/tag.h index 728c3980..76c9a82a 100644 --- a/taglib/tag.h +++ b/taglib/tag.h @@ -28,17 +28,9 @@ #include "taglib_export.h" #include "tstring.h" -#include "tmap.h" namespace TagLib { - /*! - * This is used for the unified dictionary interface: the tags of a file are - * represented as a dictionary mapping a string (the tag name) to a list of - * strings (the values). - */ - typedef Map TagDict; - //! A simple, generic interface to common audio meta data fields /*! @@ -49,6 +41,8 @@ namespace TagLib { * in TagLib::AudioProperties, TagLib::File and TagLib::FileRef. */ + class PropertyMap; + class TAGLIB_EXPORT Tag { public: @@ -59,20 +53,30 @@ namespace TagLib { virtual ~Tag(); /*! - * Unified tag dictionary interface -- export function. Converts the tags - * of the specific metadata format into a "human-readable" map of strings - * to lists of strings, being as precise as possible. + * Exports the tags of the file as dictionary mapping (human readable) tag + * names (Strings) to StringLists of tag values. + * The default implementation in this class considers only the usual built-in + * tags (artist, album, ...) and only one value per key. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Unified tag dictionary interface -- import function. Converts a map - * of strings to stringslists into the specific metadata format. Note that - * not all formats can store arbitrary tags and values, so data might - * be lost by this operation. Especially the default implementation handles - * only single values of the default tags specified in this class. + * Removes unsupported properties, or a subset of them, from the tag. + * The parameter \a properties must contain only entries from + * properties().unsupportedData(). + * BIC: Will become virtual in future releases. Currently the non-virtual + * standard implementation of TagLib::Tag does nothing, since there are + * no unsupported elements. */ - void fromDict(const TagDict &); + void removeUnsupportedProperties(const StringList& properties); + + /*! + * Sets the tags of this File to those specified in \a properties. This default + * implementation sets only the tags for which setter methods exist in this class + * (artist, album, ...), and only one value per key; the rest will be contained + * in the returned PropertyMap. + */ + PropertyMap setProperties(const PropertyMap &properties); /*! * Returns the track name; if no track name is present in the tag diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index b08efbec..639bc0ec 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -113,83 +113,116 @@ FileName File::name() const return d->stream->name(); } -TagDict File::toDict() const +PropertyMap File::properties() const { // ugly workaround until this method is virtual if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); if (dynamic_cast(this)) - return dynamic_cast(this)->toDict(); + return dynamic_cast(this)->properties(); // no specialized implementation available -> use generic one // - ASF: ugly format, largely undocumented, not worth implementing // dict interface ... // - MP4: taglib's MP4::Tag does not really support anything beyond // the basic implementation, therefor we use just the default Tag // interface - return tag()->toDict(); + return tag()->properties(); } -void File::fromDict(const TagDict &dict) +void File::removeUnsupportedProperties(const StringList &properties) +{ + // here we only consider those formats that could possibly contain + // unsupported properties + if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else if (dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); + else + tag()->removeUnsupportedProperties(properties); +} + +PropertyMap File::setProperties(const PropertyMap &properties) { if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else if (dynamic_cast(this)) - dynamic_cast(this)->fromDict(dict); + return dynamic_cast(this)->setProperties(properties); else - tag()->fromDict(dict); - + return tag()->setProperties(properties); } ByteVector File::readBlock(ulong length) diff --git a/taglib/toolkit/tfile.h b/taglib/toolkit/tfile.h index 75faf8a8..7df774a0 100644 --- a/taglib/toolkit/tfile.h +++ b/taglib/toolkit/tfile.h @@ -37,6 +37,7 @@ namespace TagLib { class String; class Tag; class AudioProperties; + class PropertyMap; //! A file class with some useful methods for tag manipulation @@ -81,16 +82,32 @@ namespace TagLib { * Exports the tags of the file as dictionary mapping (human readable) tag * names (Strings) to StringLists of tag values. Calls the according specialization * in the File subclasses. - * Will be made virtual in future releases. + * For each metadata object of the file that could not be parsed into the PropertyMap + * format, the returend map's unsupportedData() list will contain one entry identifying + * that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties() + * to remove (a subset of) them. + * BIC: Will be made virtual in future releases. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Sets the tags of this File to those specified by the given TagDict. Calls the + * Removes unsupported properties, or a subset of them, from the file's metadata. + * The parameter \a properties must contain only entries from + * properties().unsupportedData(). + * BIC: Will be mad virtual in future releases. + */ + void removeUnsupportedProperties(const StringList& properties); + + /*! + * Sets the tags of this File to those specified in \a properties. Calls the * according specialization method in the subclasses of File to do the translation * into the format-specific details. + * If some value(s) could not be written imported to the specific metadata format, + * the returned PropertyMap will contain those value(s). Otherwise it will be empty, + * indicating that no problems occured. + * BIC: will become pure virtual in the future */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &properties); /*! * Returns a pointer to this file's audio properties. This should be * reimplemented in the concrete subclasses. If no audio properties were diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 00aeca88..2e043a33 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -40,11 +40,11 @@ PropertyMap::~PropertyMap() bool PropertyMap::insert(const String &key, const StringList &values) { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return false; Iterator result = supertype::find(realKey); - if (result == end()) + if(result == end()) supertype::insert(realKey, values); else supertype::operator[](realKey).append(values); @@ -54,7 +54,7 @@ bool PropertyMap::insert(const String &key, const StringList &values) bool PropertyMap::replace(const String &key, const StringList &values) { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return false; supertype::erase(realKey); supertype::insert(realKey, values); @@ -64,7 +64,7 @@ bool PropertyMap::replace(const String &key, const StringList &values) PropertyMap::Iterator PropertyMap::find(const String &key) { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return end(); return supertype::find(realKey); } @@ -72,7 +72,7 @@ PropertyMap::Iterator PropertyMap::find(const String &key) PropertyMap::ConstIterator PropertyMap::find(const String &key) const { String realKey = prepareKey(key); - if (realKey.isNull()) + if(realKey.isNull()) return end(); return supertype::find(realKey); } @@ -80,7 +80,8 @@ PropertyMap::ConstIterator PropertyMap::find(const String &key) const bool PropertyMap::contains(const String &key) const { String realKey = prepareKey(key); - if (realKey.isNull()) + // we consider keys with empty value list as not present + if(realKey.isNull() || supertype::operator[](realKey).isEmpty()) return false; return supertype::contains(realKey); } @@ -109,13 +110,23 @@ StringList &PropertyMap::operator[](const String &key) return supertype::operator[](realKey); } +void PropertyMap::removeEmpty() +{ + StringList emptyKeys; + for(Iterator it = begin(); it != end(); ++it) + if(it->second.isEmpty()) + emptyKeys.append(it->first); + for(StringList::Iterator emptyIt = emptyKeys.begin(); emptyIt != emptyKeys.end(); emptyIt++ ) + erase(*emptyIt); +} + StringList &PropertyMap::unsupportedData() { return unsupported; } -String PropertyMap::prepareKey(const String &proposed) const { - if (proposed.isEmpty()) +static String PropertyMap::prepareKey(const String &proposed) { + if(proposed.isEmpty()) return String::null; for (String::ConstIterator it = proposed.begin(); it != proposed.end(); it++) // forbid non-printable, non-ascii, '=' (#61) and '~' (#126) diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 2025873d..4f7bf555 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -39,6 +39,11 @@ namespace TagLib { * i.e. it must contain at least one character; all printable ASCII characters * except '=' and '~' are allowed. * + * In order to be safe with other formats, keep these additional restrictions in mind: + * + * - APE only allows keys from 2 to 16 printable ASCII characters (including space), + * with the exception of these strings: ID3, TAG, OggS, MP+ + * */ class TAGLIB_EXPORT PropertyMap: public Map @@ -117,13 +122,20 @@ namespace TagLib { */ StringList &unsupportedData(); - private: + /*! + * Removes all entries which have an empty value list. + */ + void removeEmpty(); /*! * Converts \a proposed into another String suitable to be used as * a key, or returns String::null if this is not possible. */ - String prepareKey(const String &proposed) const; + static String prepareKey(const String &proposed); + + private: + + StringList unsupported; }; From a5e45f196b472db5e01b92ec84bdf5d59ac0fc56 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 21 Jan 2012 23:05:59 +0100 Subject: [PATCH 14/39] Started to work on ID3v2. --- taglib/mpc/mpcfile.cpp | 37 +++-- 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, 308 insertions(+), 94 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::fromDict(const TagDict &dict) +void MPC::File::removeUnsupportedProperties(const StringList &properties) { - if (d->hasAPE) - d->tag.access(APEIndex, false)->fromDict(dict); - else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); - else - d->tag.access(APEIndex, true)->fromDict(dict); + if(d->hasAPE) + d->tag.access(APEIndex, false)->removeUnsupportedProperties(properties); + if(d->hasID3v1) + d->tag.access(ID3v1Index, false)->removeUnsupportedProperties(properties); } +PropertyMap MPC::File::setProperties(const PropertyMap &properties) +{ + if(d->hasAPE) + return d->tag.access(APEIndex, false)->setProperties(properties); + else if(d->hasID3v1) + return d->tag.access(ID3v1Index, false)->setProperties(properties); + else + 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. From 0c8e5bbec8143b340baaf54e7a252606f520de80 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 22 Jan 2012 17:08:02 +0100 Subject: [PATCH 15/39] Implemented asProperties() in all relevant textual frames. --- taglib/CMakeLists.txt | 2 - taglib/mpeg/id3v2/frames/commentsframe.cpp | 13 +++ taglib/mpeg/id3v2/frames/commentsframe.h | 11 ++ .../id3v2/frames/textidentificationframe.cpp | 106 ++++++++++++++++-- .../id3v2/frames/textidentificationframe.h | 24 +++- .../frames/unsynchronizedlyricsframe.cpp | 7 ++ .../id3v2/frames/unsynchronizedlyricsframe.h | 7 ++ taglib/mpeg/id3v2/frames/urllinkframe.cpp | 25 +++++ taglib/mpeg/id3v2/frames/urllinkframe.h | 11 ++ taglib/mpeg/id3v2/id3v2frame.cpp | 7 +- taglib/mpeg/id3v2/id3v2frame.h | 1 + taglib/mpeg/id3v2/id3v2tag.cpp | 27 ++--- taglib/mpeg/mpegfile.cpp | 1 + taglib/tagunion.cpp | 15 --- taglib/tagunion.h | 3 - taglib/toolkit/tpropertymap.h | 2 +- 16 files changed, 212 insertions(+), 50 deletions(-) diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index a7777a5b..ca62f3a4 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 377a7ee3..5730b753 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 def01dcf..30d21ca1 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 06489ea3..44528f33 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 d4049d71..f9348438 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 0a8927e7..b3a9b3da 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 0f8260e4..f13134c4 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 09edec40..32bfda97 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 f89faad0..4eea36b3 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 3fcc8c80..9ec536e0 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 8901295a..8efe6870 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 397fe07f..0d7f5c8d 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 3a7be29e..28b8fca7 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 2ecdd6d9..52d7136b 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 20771fe8..e94d523a 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 e41c190b..1fbcb584 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. From a8632f710f6dca2d9eefb5fa98a8e37ee2f6490e Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 22 Jan 2012 22:06:24 +0100 Subject: [PATCH 16/39] More progress in ID3 ... setProperties() will get messy :( --- .../id3v2/frames/textidentificationframe.cpp | 46 +++-- .../id3v2/frames/textidentificationframe.h | 13 ++ taglib/mpeg/id3v2/frames/urllinkframe.cpp | 6 +- taglib/mpeg/id3v2/id3v2frame.cpp | 8 +- taglib/mpeg/id3v2/id3v2tag.cpp | 162 ++++++++++++++++-- 5 files changed, 202 insertions(+), 33 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 44528f33..f3aeb083 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -57,6 +57,17 @@ TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data) : setData(data); } +TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const PropertyMap &properties) // static +{ + TextIdentificationFrame* frame = TextIdentificationFrame("TIPL"); + StringList l; + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + l.append(it->first); + l.append(it->second.toString(",")); // comma-separated list of names + } + frame->setText(l); + return frame; +} TextIdentificationFrame::~TextIdentificationFrame() { delete d; @@ -92,6 +103,25 @@ void TextIdentificationFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } +// 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"} +}; + +const KeyConversionMap &TextIdentificationFrame::involvedPeopleMap() // static +{ + static KeyConversionMap m; + if(m.isEmpty()) + for(uint i = 0; i < involvedPeopleSize; ++i) + m.insert(involvedPeople[i][1], involvedPeople[i][0]); + return m; +} + PropertyMap TextIdentificationFrame::asProperties() const { if(frameID() == "TIPL") @@ -204,16 +234,6 @@ 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; @@ -249,14 +269,14 @@ PropertyMap TextIdentificationFrame::makeTMCLProperties() const return map; } for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) { - String key = PropertyMap::prepareKey(*it); - if(key.isNull()) { + String instrument = PropertyMap::prepareKey(*it); + if(instrument.isNull()) { // instrument is not a valid key -> frame unsupported map.clear(); map.unsupportedData().append(frameID()); return map; } - map.insert(key, (++it).split(",")); + map.insert(L"PERFORMER:" + instrument, (++it).split(",")); } return map; } diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.h b/taglib/mpeg/id3v2/frames/textidentificationframe.h index f9348438..fc48a99f 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.h +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.h @@ -36,6 +36,7 @@ namespace TagLib { namespace ID3v2 { class Tag; + typedef Map KeyConversionMap; //! An ID3v2 text identification frame implementation @@ -123,6 +124,13 @@ namespace TagLib { */ explicit TextIdentificationFrame(const ByteVector &data); + /*! + * This is a special factory method to create a TIPL (involved people list) + * frame from the given \a properties. Will parse key=[list of values] data + * into the TIPL format as specified in the ID3 standard. + */ + static TextIdentificationFrame *createTIPLFrame(const PropertyMap &properties); + /*! * Destroys this TextIdentificationFrame instance. */ @@ -173,6 +181,11 @@ namespace TagLib { */ StringList fieldList() const; + /*! + * Returns a KeyConversionMap mapping a role as it would be used in a PropertyMap + * to the corresponding key used in a TIPL ID3 frame to describe that role. + */ + static const KeyConversionMap &involvedPeopleMap(); PropertyMap asProperties() const; protected: diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/taglib/mpeg/id3v2/frames/urllinkframe.cpp index 32bfda97..b1800697 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -153,10 +153,12 @@ void UserUrlLinkFrame::setDescription(const String &s) PropertyMap UserUrlLinkFrame::asProperties() const { - String key = PropertyMap::prepareKey(description()); PropertyMap map; - if(key.isEmpty()) + String key; + if(description().isEmpty()) key = "URL"; + else + key = PropertyMap::prepareKey(description()); if(key.isNull()) map.unsupportedData().append(L"WXXX/" + description()); else diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 9ec536e0..bf33fa8e 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -263,7 +263,7 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc return checkEncoding(fields, encoding, header()->version()); } -static const uint frameTranslationSize = 53; +static const uint frameTranslationSize = 50; static const char *frameTranslation[][2] = { // Text information frames { "TALB", "ALBUM"}, @@ -323,10 +323,10 @@ static const char *frameTranslation[][2] = { { "WORS", "RADIOSTATIONWEBPAGE" }, { "WPAY", "PAYMENTWEBPAGE" }, { "WPUB", "PUBLISHERWEBPAGE" }, - { "WXXX", "URL"}, + //{ "WXXX", "URL"}, handled specially // Other frames - { "COMM", "COMMENT" }, - { "USLT", "LYRICS" }, + //{ "COMM", "COMMENT" }, handled specially + //{ "USLT", "LYRICS" }, handled specially }; Map &idMap() diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 0d7f5c8d..f9b5dfa8 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -342,27 +342,161 @@ PropertyMap ID3v2::Tag::properties() const return properties; } -PropertyMap ID3v2::Tag::setProperties(const PropertyMap &properties) +PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps) { FrameList toRemove; + PropertyMap properties = origProps; // first find out what frames to remove; we do not remove in-place // because that would invalidate FrameListMap iterators. - // - for (FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) - // Remove all frames which are not ignored - if (it->second.size() == 0 || !ignored(it->first) ) - toRemove.append(it->second); + // At the moment, we remove _all_ frames that don't contain unsupported data, + // and create new ones in the next step; this is to avoid clumsy technicalities + // arising when trying to do this more efficient. For example, if the current tag + // contains one URL attribute stored in an WXXX frame, but the given \a properties + // contain two URL values, we would need to remove the WXXX frame (which supports + // only one value), and create a TXXX frame with description=URL. + // The same may happen with COMM and USLT. Additionally, handling of TIPL and TMCL is + // complicated. + // In the future, someone might come up with a more clever sync algorithm. :-) + for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) { + String key = Frame::frameIDToKey(it->first); + // for unsupported (binary) frame types, as well as frames that need special treatment + // (TXXX, WXXX, COMM, TMCL, TIPL, USLT), key will be String::null + if(key.isNull()) + continue; + // else: non-user text or url frame -> there should be only one frame of this type, + // and it's asProperties() method should return a PropertyMap with exactly one key + // and empty unsupportedData(). + if(it->second.size() != 1) + debug("invalid ID3 tag: found more than one " + it->first + " frame"); + PropertyMap frameMap = it->second[0]->asProperties(); + if(properties.contains(key) && frameMap[key] == properties[key]) + properties.erase(key); + else + toRemove.append(it->second[0]); + } - for (FrameList::ConstIterator it = toRemove.begin(); it != toRemove.end(); it++) + // now handle the special cases + // first: TXXX frames + FrameList &userTextFrames = frameList("TXXX"); + for(FrameList::ConstIterator it = userTextFrames.begin(); it != userTextFrames.end(); ++it) { + PropertyMap frameMap = (*it)->asProperties(); + if(!frameMap.unsupportedData().isEmpty()) + // don't touch unsupported frames + continue; + // TXXX frames yield only one key, so it must be begin()->first + String &key = frameMap.begin()->first; + if(!Frame::keyToFrameID(key).isNull()) + // TXXX frame which a description (=key) for which there is a dedicated frame. + // We don't want this, so remove the frame, the appropriate T*** or W*** frame + // will be created later on. + toRemove.append(*it); + if(key.find(":") > 0) + // colon-separated key: this should be inside a TMCL frame. + toRemove.append(*it); + // More (ugly) exceptions: If the user provides more than one COMMENT, LYRICS, or URL + // tag, we store all of these in a TXXX, because COMM, USLT and WXXX. Otherwise there + // should not be such a TXXX frame. + if(key == "COMMENT") { + if(properties.contains("COMMENT") + && properties["COMMENT"].size() >= 2 + && properties["COMMENT"] == frameMap.begin()->second) + properties.erase("COMMENT"); + else + toRemove.append(*it); + }else if(key == "LYRICS") { + if(properties.contains("LYRICS") + && properties["LYRICS"].size() >= 2 + && properties["LYRICS"] == frameMap.begin()->second) + properties.erase("LYRICS"); + else + toRemove.append(*it); + }else if(key == "URL") { + if(properties.contains("URL") + && properties["URL"].size() >= 2 + && properties["URL"] == frameMap.begin()->second) + properties.erase("URL"); + else + toRemove.append(*it); + } + } + + // next: WXXX frames + FrameList &userUrlFrames = frameList("WXXX"); + for(FrameList::ConstIterator it = userUrlFrames.begin(); it != userUrlFrames.end(); ++it) { + PropertyMap frameMap = (*it)->asProperties(); + if(!frameMap.unsupportedData().isEmpty()) + // don't touch unsupported frames + continue; + // WXXX frames yield only one key, so it must be begin()->first + String &key = frameMap.begin()->first; + if(!Frame::keyToFrameID(key).isNull()) + // WXXX frame which a description (=key) for which there is a dedicated frame. + // We don't want this, so remove the frame, the appropriate T*** or W*** frame + // will be created later on. + toRemove.append(*it); + else if(key.find(":") > 0) + // colon-separated key: this should be inside a TMCL frame. + toRemove.append(*it); + // More exceptions: we don't allow COMMENT and LYRICS in WXXX; they should be in COMM and USLT + // (or TXXX, see above). + else if(key == "COMMENT" || key == "LYRICS") + toRemove.append(*it); + // now, the key is either URL or some other string that neither has a dedicated text frame, nor + // a colon. We accept the frame if it's contents match the values in properties. However, if + // key != URL and the values are changed, they will be stored inside a TXXX frame instead, since + // we can't distinguish free-form text from free-form URL keys (possible fix: use URL:REASON like + // in TMCL / TIPL?). + else if(properties.contains(key) && properties[key] == frameMap.begin()->second) + properties.erase(key); + else + toRemove.append(*it); + } + for(FrameList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it) removeFrame(*it); - // now create new frames from the TagDict and add them. - for (TagDict::ConstIterator it = dict.begin(); it != dict.end(); ++it) { - Frame *newFrame = createFrame(it->first, it->second); - if (newFrame) - addFrame(newFrame); - else - debug("ERROR: Don't know how to translate tag " + it->first + " to ID3v2!"); + // next: TIPL + PropertyMap existingTipl; + if(!frameList("TIPL").isEmpty()) + existingTipl = frameList("TIPL").front()->asProperties(); + PropertyMap requestedTipl; + KeyConversionMap::ConstIterator it = TextIdentificationFrame::involvedPeopleMap().begin(); + bool rebuildTipl = false; + for(; it != TextIdentificationFrame::involvedPeopleMap().end(); ++it) { + if(properties.contains(it->first)){ + requestedTipl.insert(it->first, properties[it->first]); + properties.erase(it->first); // it's ensured now that this key gets handled correctly + if(!existingTipl.contains(it->first) || existingTipl[it->first] != requestedTipl[it->first]) + rebuildTipl = true; + } else if(existingTipl.contains(it->first)) + rebuildTipl = true; + } + if(rebuildTipl){ + removeFrames("TIPL"); + addFrame(TextIdentificationFrame::createTIPLFrame(requestedTipl)); + } + + // next: create frames for everything still in properties except for TMCL ("PERFORMER:") + // keys, which are collected in a dedicated map + PropertyMap requestedTmcl; + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + if(it->first.find(":") != -1) + requestedTmcl.insert(it->first, it->second); + else{ + // phew. Now we are in the simple case that our key= pair can be represented by a + // single frame, either a T*** (not TIPL, TMCL) or W*** frame. + ByteVector id = Frame::keyToFrameID(it->first); + } + } + + // next: TMCL + PropertyMap existingTmcl; + if(!frameList("TMCL").isEmpty()) + existingTmcl = frameList("TMCL").front()->asProperties(); + bool rebuildTmcl = false; + // search for TMCL keys ("PERFORMER:") in properties + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + if(it->first.find(":") != -1) + requestedTmcl.insert(it->first, it->second); } } From d2c43d71748cdb27e8b9f48a5c60dd71be11293d Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Tue, 14 Feb 2012 21:27:14 +0100 Subject: [PATCH 17/39] ID3 interface complete; vorbis done; wav done --- taglib/mpeg/id3v2/frames/commentsframe.cpp | 4 +- taglib/mpeg/id3v2/frames/commentsframe.h | 8 +- .../id3v2/frames/textidentificationframe.cpp | 17 +- .../id3v2/frames/textidentificationframe.h | 14 ++ .../frames/unsynchronizedlyricsframe.cpp | 8 +- .../id3v2/frames/unsynchronizedlyricsframe.h | 11 ++ taglib/mpeg/id3v2/frames/urllinkframe.cpp | 2 +- taglib/mpeg/id3v2/id3v2frame.cpp | 66 ++++++- taglib/mpeg/id3v2/id3v2frame.h | 42 ++++ taglib/mpeg/id3v2/id3v2tag.cpp | 184 ++++-------------- taglib/ogg/flac/oggflacfile.cpp | 10 - taglib/ogg/flac/oggflacfile.h | 12 -- taglib/ogg/speex/speexfile.cpp | 10 - taglib/ogg/speex/speexfile.h | 11 -- taglib/ogg/vorbis/vorbisfile.cpp | 8 +- taglib/ogg/vorbis/vorbisfile.h | 12 +- taglib/riff/aiff/aifffile.cpp | 9 +- taglib/riff/aiff/aifffile.h | 12 +- taglib/riff/wav/wavfile.cpp | 8 +- taglib/riff/wav/wavfile.h | 12 +- taglib/toolkit/tpropertymap.h | 11 ++ 21 files changed, 238 insertions(+), 233 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/commentsframe.cpp b/taglib/mpeg/id3v2/frames/commentsframe.cpp index 5730b753..adc773ea 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/commentsframe.cpp @@ -109,7 +109,7 @@ void CommentsFrame::setTextEncoding(String::Type encoding) d->textEncoding = encoding; } -PropertyMap CommentsFrame::asDescription() const +PropertyMap CommentsFrame::asProperties() const { String key = PropertyMap::prepareKey(description()); PropertyMap map; @@ -118,7 +118,7 @@ PropertyMap CommentsFrame::asDescription() const if(key.isNull()) map.unsupportedData().append(L"COMM/" + description()); else - map.insert(key, text()); + map.insert("COMMENT:" + key, text()); return map; } diff --git a/taglib/mpeg/id3v2/frames/commentsframe.h b/taglib/mpeg/id3v2/frames/commentsframe.h index 30d21ca1..f65f6f01 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.h +++ b/taglib/mpeg/id3v2/frames/commentsframe.h @@ -137,15 +137,15 @@ 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" + * Parses this frame as PropertyMap with a single key. + * - if description() is empty or "COMMENT", 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. + * - otherwise, the key will be "COMMENT:" * - The single value will be the frame's text(). */ - PropertyMap asDescription() const; + PropertyMap asProperties() const; /*! * Comments each have a unique description. This searches for a comment diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index f3aeb083..29943c96 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -59,7 +59,7 @@ TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data) : TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const PropertyMap &properties) // static { - TextIdentificationFrame* frame = TextIdentificationFrame("TIPL"); + TextIdentificationFrame *frame = TextIdentificationFrame("TIPL"); StringList l; for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ l.append(it->first); @@ -68,6 +68,21 @@ TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const Property frame->setText(l); return frame; } + +TextIdentificationFrame *TextIdentificationFrame::createTMCLFrame(const PropertyMap &properties) // static +{ + TextIdentificationFrame *frame = TextIdentificationFrame("TMCL"); + StringList l; + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + if(!it->first.startsWith(instrumentPrefix)) // should not happen + continue; + l.append(it->first.substr(instrumentPrefix.size())); + l.append(it->second.toString(",")); + } + frame->setText(l); + return frame; +} + TextIdentificationFrame::~TextIdentificationFrame() { delete d; diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.h b/taglib/mpeg/id3v2/frames/textidentificationframe.h index fc48a99f..b82e7d3a 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.h +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.h @@ -131,6 +131,13 @@ namespace TagLib { */ static TextIdentificationFrame *createTIPLFrame(const PropertyMap &properties); + /*! + * This is a special factory method to create a TMCL (musician credits list) + * frame from the given \a properties. Will parse key=[list of values] data + * into the TMCL format as specified in the ID3 standard, where key should be + * of the form instrumentPrefix:instrument. + */ + static TextIdentificationFrame *createTMCLFrame(const PropertyMap &properties); /*! * Destroys this TextIdentificationFrame instance. */ @@ -186,6 +193,7 @@ namespace TagLib { * to the corresponding key used in a TIPL ID3 frame to describe that role. */ static const KeyConversionMap &involvedPeopleMap(); + PropertyMap asProperties() const; protected: @@ -243,6 +251,12 @@ namespace TagLib { */ explicit UserTextIdentificationFrame(const ByteVector &data); + /*! + * Creates a user defined text identification frame with the given \a description + * and \a text. + */ + UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding = String::Latin1); + virtual String toString() const; /*! diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index b3a9b3da..e5142566 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -113,8 +113,14 @@ void UnsynchronizedLyricsFrame::setTextEncoding(String::Type encoding) PropertyMap UnsynchronizedLyricsFrame::asProperties() const { + String key = PropertyMap::prepareKey(description()); PropertyMap map; - map.insert("LYRICS", text()); + if(key.isEmpty()) + key = "LYRICS"; + if(key.isNull()) + map.unsupportedData().append(L"USLT/" + description()); + else + map.insert("LYRICS:" + key, text()); return map; } diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h index f13134c4..03648ee4 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h @@ -139,6 +139,17 @@ namespace TagLib { * Parses this frame as PropertyMap. The returned map will contain a single key * "LYRICS" with the text() as single value. */ + /*! + * Parses this frame as PropertyMap with a single key. + * - if description() is empty or "LYRICS", the key will be "LYRICS" + * - if description() is not a valid PropertyMap key, the frame will be + * marked unsupported by an entry "USLT/" in the unsupportedData() + * attribute of the returned map. + * - otherwise, the key will be "LYRICS:" + * - The single value will be the frame's text(). + * Note that currently the language() field is not supported by the PropertyMap + * interface. + */ PropertyMap asProperties() const; protected: diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/taglib/mpeg/id3v2/frames/urllinkframe.cpp index b1800697..c0a771e1 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -162,7 +162,7 @@ PropertyMap UserUrlLinkFrame::asProperties() const if(key.isNull()) map.unsupportedData().append(L"WXXX/" + description()); else - map.insert(key, url()); + map.insert("URL:" + key, url()); return map; } diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index bf33fa8e..25e599aa 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -96,10 +96,54 @@ ByteVector Frame::textDelimiter(String::Type t) return d; } +String TextIdentificationFrame::instrumentPrefix("PERFORMER:"); +String TextIdentificationFrame::commentPrefix("COMMENT:"); +String TextIdentificationFrame::urlPrefix("URL:"); + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// +Frame *Frame::createTextualFrame(const String &key, const StringList &values) //static +{ + // check if the key is contained in the key<=>frameID mapping + ByteVector frameID = keyToFrameID(key); + if(!frameID.isNull()) { + if(frameID[0] == 'T'){ // text frame + TextIdentificationFrame* frame = TextIdentificationFrame(frameID, String::UTF8); + frame->setText(values); + return frame; + } else if(values.size() == 1){ // URL frame (not WXXX); support only one value + UrlLinkFrame* frame = UrlLinkFrame(frameID); + frame->setUrl(values.front()); + return frame; + } + } + // now we check if it's one of the "special" cases: + // -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS) + if(key == "LYRICS" && values.size() == 1){ + UnsynchronizedLyricsFrame *frame = UnsynchronizedLyricsFrame(); + frame->setText(values.front()); + return frame; + } + // -URL: depending on the number of values, use WXXX or TXXX (with description=URL) + if((key == "URL" || key.startsWith(urlPrefix)) && values.size() == 1){ + UserUrlLinkFrame *frame = UserUrlLinkFrame(String::UTF8); + frame->setDescription(key == "URL" ? key : key.substr(urlPrefix.size())); + frame->setUrl(values.front()); + return frame; + } + // -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT) + if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){ + CommentsFrame *frame = CommentsFrame(String::UTF8); + frame->setDescription(key == "COMMENT" ? key : key.substr(commentPrefix.size())); + frame->setText(values.front()); + return frame; + } + // if non of the above cases apply, we use a TXXX frame with the key as description + return UserTextIdentificationFrame(key, values, String::UTF8); +} + Frame::~Frame() { delete d; @@ -263,7 +307,7 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc return checkEncoding(fields, encoding, header()->version()); } -static const uint frameTranslationSize = 50; +static const uint frameTranslationSize = 51; static const char *frameTranslation[][2] = { // Text information frames { "TALB", "ALBUM"}, @@ -325,7 +369,7 @@ static const char *frameTranslation[][2] = { { "WPUB", "PUBLISHERWEBPAGE" }, //{ "WXXX", "URL"}, handled specially // Other frames - //{ "COMM", "COMMENT" }, handled specially + { "COMM", "COMMENT" }, //{ "USLT", "LYRICS" }, handled specially }; @@ -399,6 +443,24 @@ PropertyMap Frame::asProperties() const return m; } } + +void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties, + PropertyMap &tiplProperties, PropertyMap &tmclProperties) +{ + + singleFrameProperties.clear(); + tiplProperties.clear(); + tmclProperties.clear(); + for(PropertyMap::ConstIterator it = original.begin(); it != original.end(); ++it) { + if(TextIdentificationFrame::involvedPeopleMap().contains(it->first)) + tiplProperties.insert(it->first, it->second); + else if(it->first.startsWith(TextIdentificationFrame::instrumentPrefix)) + tmclProperties.insert(it->first, it->second); + else + singleFrameProperties.insert(it->first, it->second); + } +} + //////////////////////////////////////////////////////////////////////////////// // Frame::Header class //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 8efe6870..3a9ade26 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -57,6 +57,14 @@ namespace TagLib { friend class FrameFactory; public: + + /*! + * Creates a textual frame which corresponds to a single key in the PropertyMap + * interface. These are all (User)TextIdentificationFrames except TIPL and TMCL, + * all (User)URLLinkFrames, CommentsFrames, and UnsynchronizedLyricsFrame. + */ + static Frame *createTextualFrame(const String &key, const StringList &values); + /*! * Destroys this Frame instance. */ @@ -127,6 +135,23 @@ namespace TagLib { */ static ByteVector textDelimiter(String::Type t); + /*! + * The string with which an instrument name is prefixed to build a key in a PropertyMap; + * used to translate PropertyMaps to TMCL frames. In the current implementation, this + * is "PERFORMER:". + */ + static const String instrumentPrefix; + /*! + * The PropertyMap key prefix which triggers the use of a COMM frame instead of a TXXX + * frame for a non-standard key. In the current implementation, this is "COMMENT:". + */ + static const String commentPrefix; + /*! + * The PropertyMap key prefix which triggers the use of a WXXX frame instead of a TXX + * frame for a non-standard key. In the current implementation, this is "URL:". + */ + static const String urlPrefix; + protected: class Header; @@ -244,6 +269,23 @@ namespace TagLib { */ static String frameIDToKey(const ByteVector &); + + /*! + * This helper function splits the PropertyMap \a original into three ProperytMaps + * \a singleFrameProperties, \a tiplProperties, and \a tmclProperties, such that: + * - \a singleFrameProperties contains only of keys which can be represented with + * exactly one ID3 frame per key. In the current implementation + * this is everything except for the fixed "involved people" keys and keys of the + * form "TextIdentificationFrame::instrumentPrefix" + "instrument", which are + * mapped to a TMCL frame. + * - \a tiplProperties will consist of those keys that are present in + * TextIdentificationFrame::involvedPeopleMap() + * - \a tmclProperties contains the "musician credits" keys which should be mapped + * to a TMCL frame + */ + static void splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties, + PropertyMap &tiplProperties, PropertyMap &tmclProperties); + private: Frame(const Frame &); Frame &operator=(const Frame &); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index f9b5dfa8..f6d7e772 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -344,160 +344,46 @@ PropertyMap ID3v2::Tag::properties() const PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps) { - FrameList toRemove; - PropertyMap properties = origProps; - // first find out what frames to remove; we do not remove in-place - // because that would invalidate FrameListMap iterators. - // At the moment, we remove _all_ frames that don't contain unsupported data, - // and create new ones in the next step; this is to avoid clumsy technicalities - // arising when trying to do this more efficient. For example, if the current tag - // contains one URL attribute stored in an WXXX frame, but the given \a properties - // contain two URL values, we would need to remove the WXXX frame (which supports - // only one value), and create a TXXX frame with description=URL. - // The same may happen with COMM and USLT. Additionally, handling of TIPL and TMCL is - // complicated. - // In the future, someone might come up with a more clever sync algorithm. :-) - for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it) { - String key = Frame::frameIDToKey(it->first); - // for unsupported (binary) frame types, as well as frames that need special treatment - // (TXXX, WXXX, COMM, TMCL, TIPL, USLT), key will be String::null - if(key.isNull()) - continue; - // else: non-user text or url frame -> there should be only one frame of this type, - // and it's asProperties() method should return a PropertyMap with exactly one key - // and empty unsupportedData(). - if(it->second.size() != 1) - debug("invalid ID3 tag: found more than one " + it->first + " frame"); - PropertyMap frameMap = it->second[0]->asProperties(); - if(properties.contains(key) && frameMap[key] == properties[key]) - properties.erase(key); - else - toRemove.append(it->second[0]); - } - - // now handle the special cases - // first: TXXX frames - FrameList &userTextFrames = frameList("TXXX"); - for(FrameList::ConstIterator it = userTextFrames.begin(); it != userTextFrames.end(); ++it) { - PropertyMap frameMap = (*it)->asProperties(); - if(!frameMap.unsupportedData().isEmpty()) - // don't touch unsupported frames - continue; - // TXXX frames yield only one key, so it must be begin()->first - String &key = frameMap.begin()->first; - if(!Frame::keyToFrameID(key).isNull()) - // TXXX frame which a description (=key) for which there is a dedicated frame. - // We don't want this, so remove the frame, the appropriate T*** or W*** frame - // will be created later on. - toRemove.append(*it); - if(key.find(":") > 0) - // colon-separated key: this should be inside a TMCL frame. - toRemove.append(*it); - // More (ugly) exceptions: If the user provides more than one COMMENT, LYRICS, or URL - // tag, we store all of these in a TXXX, because COMM, USLT and WXXX. Otherwise there - // should not be such a TXXX frame. - if(key == "COMMENT") { - if(properties.contains("COMMENT") - && properties["COMMENT"].size() >= 2 - && properties["COMMENT"] == frameMap.begin()->second) - properties.erase("COMMENT"); + FrameList framesToDelete; + // we split up the PropertyMap into the "normal" keys and the "complicated" ones, + // which are those according to TIPL or TMCL frames. + PropertyMap properties; + PropertyMap tiplProperties; + PropertyMap tmclProperties; + Frame::splitProperties(origProps, properties, tiplProperties, tmclProperties); + for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it){ + for(FrameList::ConstIterator lit = it->second.begin(); lit != it->second.end(); ++lit){ + PropertyMap frameProperties = (*lit)->asProperties(); + if(it->first == "TIPL") + if (tiplProperties != frameProperties) + framesToDelete.append(*lit); + else + tiplProperties.erase(frameProperties); + else if(it->first == "TMCL") + if (tmclProperties != frameProperties) + framesToDelete.append(*lit); + else + tmclProperties.erase(frameProperties); + else if(!properties.contains(frameProperties)) + framesToDelete.append(*lit); else - toRemove.append(*it); - }else if(key == "LYRICS") { - if(properties.contains("LYRICS") - && properties["LYRICS"].size() >= 2 - && properties["LYRICS"] == frameMap.begin()->second) - properties.erase("LYRICS"); - else - toRemove.append(*it); - }else if(key == "URL") { - if(properties.contains("URL") - && properties["URL"].size() >= 2 - && properties["URL"] == frameMap.begin()->second) - properties.erase("URL"); - else - toRemove.append(*it); + properties.erase(frameProperties); } } - - // next: WXXX frames - FrameList &userUrlFrames = frameList("WXXX"); - for(FrameList::ConstIterator it = userUrlFrames.begin(); it != userUrlFrames.end(); ++it) { - PropertyMap frameMap = (*it)->asProperties(); - if(!frameMap.unsupportedData().isEmpty()) - // don't touch unsupported frames - continue; - // WXXX frames yield only one key, so it must be begin()->first - String &key = frameMap.begin()->first; - if(!Frame::keyToFrameID(key).isNull()) - // WXXX frame which a description (=key) for which there is a dedicated frame. - // We don't want this, so remove the frame, the appropriate T*** or W*** frame - // will be created later on. - toRemove.append(*it); - else if(key.find(":") > 0) - // colon-separated key: this should be inside a TMCL frame. - toRemove.append(*it); - // More exceptions: we don't allow COMMENT and LYRICS in WXXX; they should be in COMM and USLT - // (or TXXX, see above). - else if(key == "COMMENT" || key == "LYRICS") - toRemove.append(*it); - // now, the key is either URL or some other string that neither has a dedicated text frame, nor - // a colon. We accept the frame if it's contents match the values in properties. However, if - // key != URL and the values are changed, they will be stored inside a TXXX frame instead, since - // we can't distinguish free-form text from free-form URL keys (possible fix: use URL:REASON like - // in TMCL / TIPL?). - else if(properties.contains(key) && properties[key] == frameMap.begin()->second) - properties.erase(key); - else - toRemove.append(*it); - } - for(FrameList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it) + for(FrameList::ConstIterator it = framesToDelete.begin(); it != framesToDelete.end(); ++it) removeFrame(*it); - // next: TIPL - PropertyMap existingTipl; - if(!frameList("TIPL").isEmpty()) - existingTipl = frameList("TIPL").front()->asProperties(); - PropertyMap requestedTipl; - KeyConversionMap::ConstIterator it = TextIdentificationFrame::involvedPeopleMap().begin(); - bool rebuildTipl = false; - for(; it != TextIdentificationFrame::involvedPeopleMap().end(); ++it) { - if(properties.contains(it->first)){ - requestedTipl.insert(it->first, properties[it->first]); - properties.erase(it->first); // it's ensured now that this key gets handled correctly - if(!existingTipl.contains(it->first) || existingTipl[it->first] != requestedTipl[it->first]) - rebuildTipl = true; - } else if(existingTipl.contains(it->first)) - rebuildTipl = true; - } - if(rebuildTipl){ - removeFrames("TIPL"); - addFrame(TextIdentificationFrame::createTIPLFrame(requestedTipl)); - } - - // next: create frames for everything still in properties except for TMCL ("PERFORMER:") - // keys, which are collected in a dedicated map - PropertyMap requestedTmcl; - for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ - if(it->first.find(":") != -1) - requestedTmcl.insert(it->first, it->second); - else{ - // phew. Now we are in the simple case that our key= pair can be represented by a - // single frame, either a T*** (not TIPL, TMCL) or W*** frame. - ByteVector id = Frame::keyToFrameID(it->first); - } - } - - // next: TMCL - PropertyMap existingTmcl; - if(!frameList("TMCL").isEmpty()) - existingTmcl = frameList("TMCL").front()->asProperties(); - bool rebuildTmcl = false; - // search for TMCL keys ("PERFORMER:") in properties - for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ - if(it->first.find(":") != -1) - requestedTmcl.insert(it->first, it->second); - } + // now create remaining frames: + // start with the involved people list (TIPL) + if(!tiplProperties.isEmpty()) + addFrame(TextIdentificationFrame::createTIPLFrame(tiplProperties)); + // proceed with the musician credit list (TMCL) + if(!tmclProperties.isEmpty()) + addFrame(TextIdentificationFrame::createTMCLFrame(tmclProperties)); + // now create the "one key per frame" frames + for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it) + addFrame(Frame::createTextualFrame(it->first, it->second)); + return PropertyMap; // ID3 implements the complete PropertyMap interface, so an empty map is returned } ByteVector ID3v2::Tag::render() const diff --git a/taglib/ogg/flac/oggflacfile.cpp b/taglib/ogg/flac/oggflacfile.cpp index 437dabf0..3addbffa 100644 --- a/taglib/ogg/flac/oggflacfile.cpp +++ b/taglib/ogg/flac/oggflacfile.cpp @@ -92,16 +92,6 @@ Ogg::XiphComment *Ogg::FLAC::File::tag() const return d->comment; } -TagLib::TagDict Ogg::FLAC::File::toDict(void) const -{ - return d->comment->toDict(); -} - -void Ogg::FLAC::File::fromDict(const TagDict &dict) -{ - d->comment->fromDict(dict); -} - Properties *Ogg::FLAC::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/flac/oggflacfile.h b/taglib/ogg/flac/oggflacfile.h index e39ac2cc..d4373795 100644 --- a/taglib/ogg/flac/oggflacfile.h +++ b/taglib/ogg/flac/oggflacfile.h @@ -89,18 +89,6 @@ namespace TagLib { */ virtual XiphComment *tag() const; - /*! - * Implements the unified tag dictionary interface -- export function. - * Returns the contents of the Ogg::XiphComment as TagDict. - */ - TagDict toDict() const; - - /*! - * Implements the unified tag dictionary interface -- import function. - * Matches the TagDict's contents to the XiphComment of the file. - */ - void fromDict(const TagDict &); - /*! * Returns the FLAC::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 d602bcc8..3a4940a2 100644 --- a/taglib/ogg/speex/speexfile.cpp +++ b/taglib/ogg/speex/speexfile.cpp @@ -82,16 +82,6 @@ Ogg::XiphComment *Speex::File::tag() const return d->comment; } -TagLib::TagDict Ogg::Speex::File::toDict(void) const -{ - return d->comment->toDict(); -} - -void Ogg::Speex::File::fromDict(const TagDict &dict) -{ - d->comment->fromDict(dict); -} - Speex::Properties *Speex::File::audioProperties() const { return d->properties; diff --git a/taglib/ogg/speex/speexfile.h b/taglib/ogg/speex/speexfile.h index 2af6cd82..c14cf2aa 100644 --- a/taglib/ogg/speex/speexfile.h +++ b/taglib/ogg/speex/speexfile.h @@ -82,17 +82,6 @@ namespace TagLib { * TagLib::File::tag(). */ virtual Ogg::XiphComment *tag() const; - /*! - * Implements the unified tag dictionary interface -- export function. - * Returns the contents of the Ogg::XiphComment as TagDict. - */ - TagDict toDict() const; - - /*! - * Implements the unified tag dictionary interface -- import function. - * Matches the TagDict's contents to the XiphComment of the file. - */ - void fromDict(const TagDict &); /*! * Returns the Speex::Properties for this file. If no audio properties diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index fe50d6d0..9b12f496 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -85,14 +85,14 @@ Ogg::XiphComment *Vorbis::File::tag() const return d->comment; } -TagLib::TagDict Ogg::Vorbis::File::toDict(void) const +PropertyMap Vorbis::File::properties() const { - return d->comment->toDict(); + return d->comment->properties(); } -void Ogg::Vorbis::File::fromDict(const TagDict &dict) +PropertyMap Vorbis::File::setProperties(const PropertyMap &properties) { - d->comment->fromDict(dict); + return d->comment->setProperties(properties); } Vorbis::Properties *Vorbis::File::audioProperties() const diff --git a/taglib/ogg/vorbis/vorbisfile.h b/taglib/ogg/vorbis/vorbisfile.h index 989eac3d..15c29d99 100644 --- a/taglib/ogg/vorbis/vorbisfile.h +++ b/taglib/ogg/vorbis/vorbisfile.h @@ -90,17 +90,19 @@ namespace TagLib { */ virtual Ogg::XiphComment *tag() const; + /*! - * Implements the unified tag dictionary interface -- export function. - * Returns the contents of the Ogg::XiphComment as TagDict. + * Implements the unified property interface -- export function. + * This forwards directly to XiphComment::properties(). */ - TagDict toDict() const; + PropertyMap properties() const; /*! * Implements the unified tag dictionary interface -- import function. - * Matches the TagDict's contents to the XiphComment of the file. + * Like properties(), this is a forwarder to the file's XiphComment. */ - void fromDict(const TagDict &); + 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 5a00b807..bc2a8569 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -84,15 +84,14 @@ ID3v2::Tag *RIFF::AIFF::File::tag() const return d->tag; } -TagLib::TagDict RIFF::AIFF::File::toDict(void) const +PropertyMap RIFF::AIFF::File::properties() const { - return d->tag->toDict(); - + return d->tag->properties(); } -void RIFF::AIFF::File::fromDict(const TagDict &dict) +PropertyMap RIFF::AIFF::File::setProperties(const PropertyMap &properties) { - d->tag->fromDict(dict); + return d->tag->setProperties(properties); } diff --git a/taglib/riff/aiff/aifffile.h b/taglib/riff/aiff/aifffile.h index af7d7b7a..a50c8ecb 100644 --- a/taglib/riff/aiff/aifffile.h +++ b/taglib/riff/aiff/aifffile.h @@ -84,16 +84,16 @@ namespace TagLib { virtual ID3v2::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. - * This method forwards to ID3v2::Tag::toDict. + * Implements the unified property interface -- export function. + * This method forwards to ID3v2::Tag::properties(). */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * This method forwards to ID3v2::Tag::fromDict. + * Implements the unified property interface -- import function. + * This method forwards to ID3v2::Tag::setProperties(). */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the AIFF::Properties for this file. If no audio properties diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index cb274dd1..afc307d8 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -84,14 +84,14 @@ ID3v2::Tag *RIFF::WAV::File::tag() const return d->tag; } -TagLib::TagDict RIFF::WAV::File::toDict(void) const +PropertyMap RIFF::WAV::File::properties() const { - return d->tag->toDict(); + return d->tag->properties(); } -void RIFF::WAV::File::fromDict(const TagDict &dict) +PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties) { - d->tag->fromDict(dict); + return d->tag->setProperties(properties); } diff --git a/taglib/riff/wav/wavfile.h b/taglib/riff/wav/wavfile.h index 8e75afdb..861f3f77 100644 --- a/taglib/riff/wav/wavfile.h +++ b/taglib/riff/wav/wavfile.h @@ -84,16 +84,16 @@ namespace TagLib { virtual ID3v2::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. - * This method forwards to ID3v2::Tag::toDict. + * Implements the unified property interface -- export function. + * This method forwards to ID3v2::Tag::properties(). */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * This method forwards to ID3v2::Tag::fromDict. + * Implements the unified property interface -- import function. + * This method forwards to ID3v2::Tag::setProperties(). */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the WAV::Properties for this file. If no audio properties diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 1fbcb584..071c3e9b 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -91,11 +91,22 @@ namespace TagLib { */ bool contains(const String &key) const; + /*! + * Returns true if this map contains all keys of \a other + * and the values coincide for that keys. + */ + bool contains(const PropertyMap &other) const; + /*! * Erase the \a key and its values from the map. */ PropertyMap &erase(const String &key); + /*! + * Erases from this map all keys that appear in \a other. + */ + PropertyMap &erase(const PropertyMap &other); + /*! * Merge the contents of \a other into this PropertyMap. * If a key is contained in both maps, the values of the second From 48aaaf8dc447655ec189e88caf45e6aa3d030b75 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Tue, 14 Feb 2012 21:29:30 +0100 Subject: [PATCH 18/39] Ported s3m; removed old id3v2dicttools. --- taglib/mpeg/id3v2/id3v2dicttools.cpp | 368 --------------------------- taglib/mpeg/id3v2/id3v2dicttools.h | 103 -------- taglib/s3m/s3mfile.cpp | 8 +- taglib/s3m/s3mfile.h | 12 +- 4 files changed, 10 insertions(+), 481 deletions(-) delete mode 100644 taglib/mpeg/id3v2/id3v2dicttools.cpp delete mode 100644 taglib/mpeg/id3v2/id3v2dicttools.h diff --git a/taglib/mpeg/id3v2/id3v2dicttools.cpp b/taglib/mpeg/id3v2/id3v2dicttools.cpp deleted file mode 100644 index 63a0398b..00000000 --- a/taglib/mpeg/id3v2/id3v2dicttools.cpp +++ /dev/null @@ -1,368 +0,0 @@ -/*************************************************************************** - copyright : (C) 2011 by Michael Helmling - email : supermihi@web.de - ***************************************************************************/ - -/*************************************************************************** - * This library is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Lesser General Public License version * - * 2.1 as published by the Free Software Foundation. * - * * - * This library is distributed in the hope that it will be useful, but * - * WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU Lesser General Public * - * License along with this library; if not, write to the Free Software * - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * - * 02110-1301 USA * - * * - * Alternatively, this file is available under the Mozilla Public * - * License Version 1.1. You may obtain a copy of the License at * - * http://www.mozilla.org/MPL/ * - ***************************************************************************/ -#include "tdebug.h" -#include "id3v2dicttools.h" -#include "tmap.h" - -#include "frames/textidentificationframe.h" -#include "frames/commentsframe.h" -#include "frames/urllinkframe.h" -#include "frames/uniquefileidentifierframe.h" -#include "frames/unsynchronizedlyricsframe.h" -#include "id3v1genres.h" - -namespace TagLib { - namespace ID3v2 { - - // 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; - } - - /*! - * A map of translations frameID <-> tag used by the unified dictionary interface. - */ - static const uint numid3frames = 55; - static const char *id3frames[][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 < numid3frames; ++i) - m[id3frames[i][0]] = id3frames[i][1]; - return m; - } - - // list of TXXX frame description conversions - static const uint txxxConversionSize = 4; - static const char *txxxConversionFrames[][2] = { - {"MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID"}, - {"MusicBrainz Disc Id", "MUSICBRAINZ_DISCID"}, - {"MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID"}, - {"MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID"}, - {"MusicMagic Fingerprint", "MUSICIP_FINGERPRINT"}, - {"MusicIP PUID", "MUSICIP_PUID"}, - {"MusicBrainz Album Release Country", "RELEASECOUNTRY"}, - {"MusicBrainz Album Status", "MUSICBRAINZ_ALBUMSTATUS"}, - {"MusicBrainz Album Type", "MUSICBRAINZ_ALBUMTYPE"}, - {"MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASE_GROUPID"}, - {"MusicBrainz Work Id", "MUSICBRAINZ_WORKID"}, - {"MusicBrainz Original Album Id", "MUSICBRAINZ_ORIGINALALBUMID"}, - {"Acoustid Fingerprint", "ACOUSTID_FINGERPRINT"}, - {"Acoustid Id", "ACOUSTID_ID"} - }; - - FrameIDMap &txxxConversionMap() - { - static FrameIDMap txxxMap; - if (txxxMap.isEmpty()) - for(uint i = 0; i < txxxConversionSize; ++i) - txxxMap[txxxConversionFrames[i][0]] = txxxConversionFrames[i][1]; - return txxxMap; - } - String frameIDToTagName(const ByteVector &id) - { - Map &m = idMap(); - if (m.contains(id)) - return m[id]; - if (deprecationMap().contains(id)) - return m[deprecationMap()[id]]; - debug("unknown frame ID in frameIDToTagName(): " + id); - return "UNKNOWNID3TAG#" + String(id) + "#"; //TODO: implement this nicer - } - - ByteVector tagNameToFrameID(const String &s) - { - static Map m; - if (m.isEmpty()) - for (size_t i = 0; i < numid3frames; ++i) - m[id3frames[i][1]] = id3frames[i][0]; - if (m.contains(s.upper())) - return m[s]; - return "TXXX"; - } - - bool ignored(const ByteVector& id) - { - return !(id == "TXXX") && !idMap().contains(id) && !deprecated(id); - } - - bool deprecated(const ByteVector& id) - { - return deprecationMap().contains(id); - } - - String prepareTagName(const String &s) { - int pos = s.find("::"); - return ((pos != -1) ? s.substr(pos+2) : s).upper(); - } - /* - * The following _parseXXX functions are to be replaced by implementations of a virtual - * function in ID3v2::Frame ASAP. - */ - KeyValuePair _parseUserTextIdentificationFrame(const UserTextIdentificationFrame *frame) - { - String tagName = frame->description(); - StringList l(frame->fieldList()); - // this is done because taglib stores the description also as first entry - // in the field list. (why?) - if (l.contains(tagName)) - l.erase(l.find(tagName)); - return KeyValuePair(prepareTagName(tagName), l); - } - - Frame *_createUserTextIdentificationFrame(const String &tag, const StringList &values) - { - UserTextIdentificationFrame* frame = new UserTextIdentificationFrame(); - frame->setDescription(tag); - frame->setText(values); - return frame; - } - - KeyValuePair _parseTextIdentificationFrame(const TextIdentificationFrame *frame) - { - String tagName = frameIDToTagName(frame->frameID()); - StringList l = frame->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 lit = l.begin(); lit != l.end(); ++lit) { - bool ok = false; - int test = lit->toInt(&ok); // test if the genre value is an integer - if (ok) - *lit = ID3v1::genre(test); - } - } - else if (tagName == "DATE") { - for (StringList::Iterator lit = l.begin(); lit != l.end(); ++lit) { - // 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 = lit->find("T"); - if (tpos != -1) - (*lit)[tpos] = ' '; - } - } - return KeyValuePair(tagName, l); - } - - Frame *_createTextIdentificationFrame(const String &tag, const StringList &values) - { - StringList newValues(values); // create a copy because the following might modify - // the easiest case: a normal text frame - if (tag == "DATE") { - // Handle ISO8601 date format - for (StringList::Iterator lit = newValues.begin(); lit != newValues.end(); ++lit) - if (lit->length() > 10 && (*lit)[10] == ' ') - (*lit)[10] = 'T'; - } - TextIdentificationFrame *frame = new TextIdentificationFrame(tagNameToFrameID(tag)); - frame->setText(newValues); - return frame; - } - - KeyValuePair _parseUserUrlLinkFrame(const UserUrlLinkFrame *frame) - { - String tagName = frame->description().upper(); - if (tagName == "") - tagName = "URL"; - return KeyValuePair(tagName, frame->url()); - } - - /*! - * Create a UserUrlLinkFrame. Note that this is valid only if values.size() == 1. - */ - Frame *_createUserUrlLinkFrame(const String &tag, const StringList &values) - { - UserUrlLinkFrame* frame = new UserUrlLinkFrame(); - frame->setDescription(tag); - frame->setUrl(values[0]); - return frame; - } - - KeyValuePair _parseUrlLinkFrame(const UrlLinkFrame *frame) - { - return KeyValuePair(frameIDToTagName(frame->frameID()) , frame->url()); - } - - /*! - * Create a rUrlLinkFrame. Note that this is valid only if values.size() == 1. - */ - Frame *_createUrlLinkFrame(const String &tag, const StringList &values) - { - UrlLinkFrame *frame = new UrlLinkFrame(tagNameToFrameID(tag)); - frame->setUrl(values[0]); - return frame; - } - - KeyValuePair _parseCommentsFrame(const CommentsFrame *frame) - { - String tagName = frame->description().upper(); - if (tagName.isEmpty()) - tagName = "COMMENT"; - return KeyValuePair(tagName, frame->text()); - } - - Frame *_createCommentsFrame(const String &tag, const StringList &values) - { - CommentsFrame *frame = new CommentsFrame(String::UTF8); - frame->setText(values[0]); - return frame; - } - - KeyValuePair _parseUnsynchronizedLyricsFrame(const UnsynchronizedLyricsFrame *frame) - { - return KeyValuePair("LYRICS", frame->text()); - } - - Frame *_createUnsynchronizedLyricsFrame(const String &tag, const StringList &values) - { - UnsynchronizedLyricsFrame* frame = new UnsynchronizedLyricsFrame(); - frame->setDescription(""); - frame->setText(values[0]); - return frame; - } - - KeyValuePair parseFrame(const Frame *frame) - { - const ByteVector &id = frame->frameID(); - if (id == "TXXX") - return _parseUserTextIdentificationFrame(dynamic_cast< const UserTextIdentificationFrame* >(frame)); - else if (id[0] == 'T') - return _parseTextIdentificationFrame(dynamic_cast(frame)); - else if (id == "WXXX") - return _parseUserUrlLinkFrame(dynamic_cast< const UserUrlLinkFrame* >(frame)); - else if (id[0] == 'W') - return _parseUrlLinkFrame(dynamic_cast< const UrlLinkFrame* >(frame)); - else if (id == "COMM") - return _parseCommentsFrame(dynamic_cast< const CommentsFrame* >(frame)); - else if (id == "USLT") - return _parseUnsynchronizedLyricsFrame(dynamic_cast< const UnsynchronizedLyricsFrame* >(frame)); - else { - debug("parsing unknown ID3 frame: " + id); - return KeyValuePair("UNKNOWNID3TAG", frame->toString()); - } - } - - Frame *createFrame(const String &tag, const StringList &values) - { - ByteVector id = tagNameToFrameID(tag); - if (id == "TXXX" || - ((id[0] == 'W' || id == "COMM" || id == "USLT") && values.size() > 1)) - return _createUserTextIdentificationFrame(tag, values); - else if (id[0] == 'T') - return _createTextIdentificationFrame(tag, values); - else if (id == "WXXX") - return _createUserUrlLinkFrame(tag, values); - else if (id[0] == 'W') - return _createUrlLinkFrame(tag, values); - else if (id == "COMM") - return _createCommentsFrame(tag, values); - else if (id == "USLT") - return _createUnsynchronizedLyricsFrame(tag, values); - return 0; - } - } -} diff --git a/taglib/mpeg/id3v2/id3v2dicttools.h b/taglib/mpeg/id3v2/id3v2dicttools.h deleted file mode 100644 index 1f831c63..00000000 --- a/taglib/mpeg/id3v2/id3v2dicttools.h +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************** - copyright : (C) 2011 by Michael Helmling - email : supermihi@web.de - ***************************************************************************/ - -/*************************************************************************** - * This library is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Lesser General Public License version * - * 2.1 as published by the Free Software Foundation. * - * * - * This library is distributed in the hope that it will be useful, but * - * WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * - * Lesser General Public License for more details. * - * * - * You should have received a copy of the GNU Lesser General Public * - * License along with this library; if not, write to the Free Software * - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * - * 02110-1301 USA * - * * - * Alternatively, this file is available under the Mozilla Public * - * License Version 1.1. You may obtain a copy of the License at * - * http://www.mozilla.org/MPL/ * - ***************************************************************************/ - -#ifndef ID3V2DICTTOOLS_H_ -#define ID3V2DICTTOOLS_H_ - -#include "tstringlist.h" -#include "taglib_export.h" -#include "tmap.h" -#include - -namespace TagLib { - namespace ID3v2 { - /*! - * This file contains methods used by the unified dictionary interface for ID3v2 tags - * (tag name conversion, handling of un-translatable frameIDs, ...). - */ - - typedef Map FrameIDMap; - typedef std::pair KeyValuePair; - - class Frame; - /*! - * Returns an appropriate ID3 frame ID for the given free-form tag name. This method - * will return TXXX if no specialized translation is found. - */ - ByteVector TAGLIB_EXPORT tagNameToFrameID(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. - */ - String TAGLIB_EXPORT frameIDToTagName(const ByteVector &); - - /*! - * Tell if the given frame ID is ignored by the unified dictionary subsystem. This is true - * for frames that don't admit a textual representation, such as pictures or other binary - * information, as well as invalid frames that violate the ID3 specification. - * - * These include: - * - illegal frames violating the specification but seem to be used by some applications, e.g. - * "TCMP", illegal 'Part of Compilation' frame set by iTunes (see http://www.id3.org/Compliance_Issues) - * "NCON", illegal MusicMatch frame (see http://www.id3.org/Compliance_Issues) - * - * - frames without a meaningful textual representation -- might be implemented in some future release - * "GEOB", no way to handle a general encapsulated object by the dict interface - * "PRIV", private frames - * "APIC", attached picture - * "POPM", popularimeter - * "RVA2", relative volume - * "UFID", unique file identifier - */ - bool TAGLIB_EXPORT ignored(const ByteVector &); - - /*! - * Returns true if the given frame ID is deprecated according to the most recent ID3v2 standard. - */ - bool TAGLIB_EXPORT deprecated(const ByteVector&); - - /*! - * Parse the ID3v2::Frame *Frame* to a pair of a human-readable key (e.g. ARTIST) and - * a StringList containing the values. - */ - KeyValuePair parseFrame(const Frame*); - - /*! - * Create an appropriate ID3v2::Frame for the given tag name and values. - */ - Frame *createFrame(const String &tag, const StringList &values); - /*! - * prepare the given tag name for use in a unified dictionary: make it uppercase and - * removes prefixes set by the ExFalso/QuodLibet package. - */ - String prepareTagName(const String &); - - - } -} - - -#endif /* ID3V2DICTTOOLS_H_ */ diff --git a/taglib/s3m/s3mfile.cpp b/taglib/s3m/s3mfile.cpp index bf487aef..cf8a7b61 100644 --- a/taglib/s3m/s3mfile.cpp +++ b/taglib/s3m/s3mfile.cpp @@ -67,14 +67,14 @@ Mod::Tag *S3M::File::tag() const return &d->tag; } -TagDict S3M::File::toDict() const +PropertyMap S3M::File::properties() const { - return d->tag.toDict(); + return d->tag.properties(); } -void S3M::File::fromDict(const TagDict &tagDict) +PropertyMap S3M::File::setProperties(const PropertyMap &properties) { - d->tag.fromDict(tagDict); + return d->tag.setProperties(properties); } S3M::Properties *S3M::File::audioProperties() const diff --git a/taglib/s3m/s3mfile.h b/taglib/s3m/s3mfile.h index 99c0402b..0605b2bf 100644 --- a/taglib/s3m/s3mfile.h +++ b/taglib/s3m/s3mfile.h @@ -61,16 +61,16 @@ namespace TagLib { Mod::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. - * Forwards to Mod::Tag::toDict(). + * Implements the unified property interface -- export function. + * Forwards to Mod::Tag::properties(). */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * Forwards to Mod::Tag::fromDict(). + * Implements the unified property interface -- import function. + * Forwards to Mod::Tag::setProperties(). */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the S3M::Properties for this file. If no audio properties From 2185d52f561811582ae2c8b24ea04d7f4beca497 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Tue, 14 Feb 2012 21:32:36 +0100 Subject: [PATCH 19/39] Ported trueaudio. --- taglib/trueaudio/trueaudiofile.cpp | 18 +++++++++--------- taglib/trueaudio/trueaudiofile.h | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index 6875dda7..346331df 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -127,25 +127,25 @@ TagLib::Tag *TrueAudio::File::tag() const return &d->tag; } -TagLib::TagDict TrueAudio::File::toDict(void) const +PropertyMap TrueAudio::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(); + return d->tag.access(ID3v2Index, false)->properties(); if (d->hasID3v1) - return d->tag.access(ID3v1Index, false)->toDict(); - return TagLib::TagDict(); + return d->tag.access(ID3v1Index, false)->properties(); + return PropertyMap(); } -void TrueAudio::File::fromDict(const TagDict &dict) +PropertyMap TrueAudio::File::setProperties(const PropertyMap &properties) { if (d->hasID3v2) - d->tag.access(ID3v2Index, false)->fromDict(dict); + return d->tag.access(ID3v2Index, false)->setProperties(properties); else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); + return d->tag.access(ID3v1Index, false)->setProperties(properties); else - d->tag.access(ID3v2Index, true)->fromDict(dict); + return d->tag.access(ID3v2Index, true)->setProperties(properties); } TrueAudio::Properties *TrueAudio::File::audioProperties() const diff --git a/taglib/trueaudio/trueaudiofile.h b/taglib/trueaudio/trueaudiofile.h index 2489c7c4..9b0378f7 100644 --- a/taglib/trueaudio/trueaudiofile.h +++ b/taglib/trueaudio/trueaudiofile.h @@ -125,18 +125,18 @@ 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 ID3v1 and v2 tags, only ID3v2 will be - * converted to the TagDict. + * converted to the PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * 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, ID3v2 will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the TrueAudio::Properties for this file. If no audio properties From d6215365a115fa42ff741737ce35a22e712a2917 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Tue, 14 Feb 2012 21:34:43 +0100 Subject: [PATCH 20/39] Ported wavpack. --- taglib/wavpack/wavpackfile.cpp | 16 ++++++++-------- taglib/wavpack/wavpackfile.h | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 6afa188d..bc0f607a 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -105,23 +105,23 @@ TagLib::Tag *WavPack::File::tag() const return &d->tag; } -TagLib::TagDict WavPack::File::toDict(void) const +PropertyMap WavPack::File::properties() const { if (d->hasAPE) - return d->tag.access(APEIndex, false)->toDict(); + return d->tag.access(APEIndex, false)->properties(); if (d->hasID3v1) - return d->tag.access(ID3v1Index, false)->toDict(); - return TagLib::TagDict(); + return d->tag.access(ID3v1Index, false)->properties(); + return PropertyMap(); } -void WavPack::File::fromDict(const TagDict &dict) +PropertyMap WavPack::File::setProperties(const PropertyMap &properties) { if (d->hasAPE) - d->tag.access(APEIndex, false)->fromDict(dict); + return d->tag.access(APEIndex, false)->setProperties(properties); else if (d->hasID3v1) - d->tag.access(ID3v1Index, false)->fromDict(dict); + return d->tag.access(ID3v1Index, false)->setProperties(properties); else - d->tag.access(APE, true)->fromDict(dict); + return d->tag.access(APE, true)->setProperties(properties); } WavPack::Properties *WavPack::File::audioProperties() const diff --git a/taglib/wavpack/wavpackfile.h b/taglib/wavpack/wavpackfile.h index dcc57107..02bac023 100644 --- a/taglib/wavpack/wavpackfile.h +++ b/taglib/wavpack/wavpackfile.h @@ -107,18 +107,18 @@ 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 APE - * will be converted to the TagDict. + * will be converted to the PropertyMap. */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. + * Implements the unified property interface -- import function. * As for the export, only one tag is taken into account. If the file * has no tag at all, APE will be created. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap&); /*! * Returns the MPC::Properties for this file. If no audio properties From 8a8e9b702cf34b6d206c32b1226fbf71f1fd86ab Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Tue, 14 Feb 2012 21:35:50 +0100 Subject: [PATCH 21/39] Ported xm. --- taglib/xm/xmfile.cpp | 8 ++++---- taglib/xm/xmfile.h | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/taglib/xm/xmfile.cpp b/taglib/xm/xmfile.cpp index bd450c85..d4225c45 100644 --- a/taglib/xm/xmfile.cpp +++ b/taglib/xm/xmfile.cpp @@ -379,14 +379,14 @@ Mod::Tag *XM::File::tag() const return &d->tag; } -TagDict XM::File::toDict() const +PropertyMap XM::File::properties() const { - return d->tag.toDict(); + return d->tag.properties(); } -void XM::File::fromDict(const TagDict &tagDict) +PropertyMap XM::File::setProperties(const PropertyMap &properties) { - d->tag.fromDict(tagDict); + return d->tag.setProperties(properties); } XM::Properties *XM::File::audioProperties() const diff --git a/taglib/xm/xmfile.h b/taglib/xm/xmfile.h index 27257fca..1b07010b 100644 --- a/taglib/xm/xmfile.h +++ b/taglib/xm/xmfile.h @@ -61,16 +61,16 @@ namespace TagLib { Mod::Tag *tag() const; /*! - * Implements the unified tag dictionary interface -- export function. - * Forwards to Mod::Tag::toDict(). + * Implements the unified property interface -- export function. + * Forwards to Mod::Tag::properties(). */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * Forwards to Mod::Tag::fromDict(). + * Implements the unified property interface -- import function. + * Forwards to Mod::Tag::setProperties(). */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap &); /*! * Returns the XM::Properties for this file. If no audio properties From 140f4a57e24d1097190997bc5ace60425532fafa Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Tue, 14 Feb 2012 22:11:30 +0100 Subject: [PATCH 22/39] fixed lots of bugs found by 'make' --- taglib/mpeg/id3v2/frames/commentsframe.cpp | 1 + .../id3v2/frames/textidentificationframe.cpp | 20 ++++++---- .../frames/unsynchronizedlyricsframe.cpp | 1 + taglib/mpeg/id3v2/frames/urllinkframe.cpp | 1 + taglib/mpeg/id3v2/id3v2frame.cpp | 26 +++++++------ taglib/mpeg/id3v2/id3v2tag.cpp | 8 ++-- taglib/ogg/vorbis/vorbisfile.cpp | 2 + taglib/ogg/xiphcomment.cpp | 38 +++++++++---------- taglib/ogg/xiphcomment.h | 12 +++--- taglib/toolkit/tpropertymap.cpp | 13 ++++++- taglib/toolkit/tpropertymap.h | 20 +++++++++- 11 files changed, 91 insertions(+), 51 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/commentsframe.cpp b/taglib/mpeg/id3v2/frames/commentsframe.cpp index adc773ea..2b59db4d 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/commentsframe.cpp @@ -29,6 +29,7 @@ #include #include "commentsframe.h" +#include "tpropertymap.h" using namespace TagLib; using namespace ID3v2; diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 29943c96..6a9d760b 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -27,6 +27,8 @@ #include #include "textidentificationframe.h" +#include "tpropertymap.h" +#include "id3v1genres.h" using namespace TagLib; using namespace ID3v2; @@ -59,7 +61,7 @@ TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data) : TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const PropertyMap &properties) // static { - TextIdentificationFrame *frame = TextIdentificationFrame("TIPL"); + TextIdentificationFrame *frame = new TextIdentificationFrame("TIPL"); StringList l; for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ l.append(it->first); @@ -71,7 +73,7 @@ TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const Property TextIdentificationFrame *TextIdentificationFrame::createTMCLFrame(const PropertyMap &properties) // static { - TextIdentificationFrame *frame = TextIdentificationFrame("TMCL"); + TextIdentificationFrame *frame = new TextIdentificationFrame("TMCL"); StringList l; for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){ if(!it->first.startsWith(instrumentPrefix)) // should not happen @@ -120,12 +122,12 @@ void TextIdentificationFrame::setTextEncoding(String::Type encoding) // array of allowed TIPL prefixes and their corresponding key value static const uint involvedPeopleSize = 5; -static const char* involvedPeople[2] = { +static const char* involvedPeople[][2] = { {"ARRANGER", "ARRANGER"}, {"ENGINEER", "ENGINEER"}, {"PRODUCER", "PRODUCER"}, {"DJ-MIX", "DJMIXER"}, - {"MIX", "MIXER"} + {"MIX", "MIXER"}, }; const KeyConversionMap &TextIdentificationFrame::involvedPeopleMap() // static @@ -144,7 +146,7 @@ PropertyMap TextIdentificationFrame::asProperties() const if(frameID() == "TMCL") return makeTMCLProperties(); PropertyMap map; - String tagName = frameIDToTagName(frameID()); + String tagName = frameIDToKey(frameID()); if(tagName.isNull()) { map.unsupportedData().append(frameID()); return map; @@ -168,7 +170,9 @@ PropertyMap TextIdentificationFrame::asProperties() const (*it)[tpos] = ' '; } } - return KeyValuePair(tagName, values); + PropertyMap ret; + ret.insert(tagName, values); + return ret; } //////////////////////////////////////////////////////////////////////////////// @@ -261,7 +265,7 @@ PropertyMap TextIdentificationFrame::makeTIPLProperties() const bool found = false; for(uint i = 0; i < involvedPeopleSize; ++i) if(*it == involvedPeople[i][0]) { - map.insert(involvedPeople[i][1], (++it).split(",")); + map.insert(involvedPeople[i][1], (++it)->split(",")); found = true; break; } @@ -291,7 +295,7 @@ PropertyMap TextIdentificationFrame::makeTMCLProperties() const map.unsupportedData().append(frameID()); return map; } - map.insert(L"PERFORMER:" + instrument, (++it).split(",")); + map.insert(L"PERFORMER:" + instrument, (++it)->split(",")); } return map; } diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index e5142566..ae013ec9 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -28,6 +28,7 @@ #include "unsynchronizedlyricsframe.h" #include #include +#include using namespace TagLib; using namespace ID3v2; diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/taglib/mpeg/id3v2/frames/urllinkframe.cpp index c0a771e1..db872db2 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -28,6 +28,7 @@ #include "urllinkframe.h" #include #include +#include using namespace TagLib; using namespace ID3v2; diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 25e599aa..16b02d55 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -39,6 +39,10 @@ #include "id3v2frame.h" #include "id3v2synchdata.h" #include "tpropertymap.h" +#include "frames/textidentificationframe.h" +#include "frames/urllinkframe.h" +#include "frames/unsynchronizedlyricsframe.h" +#include "frames/commentsframe.h" using namespace TagLib; using namespace ID3v2; @@ -96,9 +100,9 @@ ByteVector Frame::textDelimiter(String::Type t) return d; } -String TextIdentificationFrame::instrumentPrefix("PERFORMER:"); -String TextIdentificationFrame::commentPrefix("COMMENT:"); -String TextIdentificationFrame::urlPrefix("URL:"); +const String Frame::instrumentPrefix("PERFORMER:"); +const String Frame::commentPrefix("COMMENT:"); +const String Frame::urlPrefix("URL:"); //////////////////////////////////////////////////////////////////////////////// // public members @@ -110,11 +114,11 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // ByteVector frameID = keyToFrameID(key); if(!frameID.isNull()) { if(frameID[0] == 'T'){ // text frame - TextIdentificationFrame* frame = TextIdentificationFrame(frameID, String::UTF8); + TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8); frame->setText(values); return frame; } else if(values.size() == 1){ // URL frame (not WXXX); support only one value - UrlLinkFrame* frame = UrlLinkFrame(frameID); + UrlLinkFrame* frame = new UrlLinkFrame(frameID); frame->setUrl(values.front()); return frame; } @@ -122,26 +126,26 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // // now we check if it's one of the "special" cases: // -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS) if(key == "LYRICS" && values.size() == 1){ - UnsynchronizedLyricsFrame *frame = UnsynchronizedLyricsFrame(); + UnsynchronizedLyricsFrame *frame = new UnsynchronizedLyricsFrame(); frame->setText(values.front()); return frame; } // -URL: depending on the number of values, use WXXX or TXXX (with description=URL) if((key == "URL" || key.startsWith(urlPrefix)) && values.size() == 1){ - UserUrlLinkFrame *frame = UserUrlLinkFrame(String::UTF8); + UserUrlLinkFrame *frame = new UserUrlLinkFrame(String::UTF8); frame->setDescription(key == "URL" ? key : key.substr(urlPrefix.size())); frame->setUrl(values.front()); return frame; } // -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT) if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){ - CommentsFrame *frame = CommentsFrame(String::UTF8); + CommentsFrame *frame = new CommentsFrame(String::UTF8); frame->setDescription(key == "COMMENT" ? key : key.substr(commentPrefix.size())); frame->setText(values.front()); return frame; } // if non of the above cases apply, we use a TXXX frame with the key as description - return UserTextIdentificationFrame(key, values, String::UTF8); + return new UserTextIdentificationFrame(key, values, String::UTF8); } Frame::~Frame() @@ -391,9 +395,9 @@ static const char *deprecatedFrames[][2] = { {"TIME", "TDRC"}, // 2.3 -> 2.4 }; -FrameIDMap &deprecationMap() +Map &deprecationMap() { - static FrameIDMap depMap; + static Map depMap; if(depMap.isEmpty()) for(uint i = 0; i < deprecatedFramesSize; ++i) depMap[deprecatedFrames[i][0]] = deprecatedFrames[i][1]; diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index f6d7e772..05d58f48 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -354,17 +354,17 @@ PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps) for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it){ for(FrameList::ConstIterator lit = it->second.begin(); lit != it->second.end(); ++lit){ PropertyMap frameProperties = (*lit)->asProperties(); - if(it->first == "TIPL") + if(it->first == "TIPL") { if (tiplProperties != frameProperties) framesToDelete.append(*lit); else tiplProperties.erase(frameProperties); - else if(it->first == "TMCL") + } else if(it->first == "TMCL") { if (tmclProperties != frameProperties) framesToDelete.append(*lit); else tmclProperties.erase(frameProperties); - else if(!properties.contains(frameProperties)) + } else if(!properties.contains(frameProperties)) framesToDelete.append(*lit); else properties.erase(frameProperties); @@ -383,7 +383,7 @@ PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps) // now create the "one key per frame" frames for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it) addFrame(Frame::createTextualFrame(it->first, it->second)); - return PropertyMap; // ID3 implements the complete PropertyMap interface, so an empty map is returned + return PropertyMap(); // ID3 implements the complete PropertyMap interface, so an empty map is returned } ByteVector ID3v2::Tag::render() const diff --git a/taglib/ogg/vorbis/vorbisfile.cpp b/taglib/ogg/vorbis/vorbisfile.cpp index 9b12f496..e2eed9e2 100644 --- a/taglib/ogg/vorbis/vorbisfile.cpp +++ b/taglib/ogg/vorbis/vorbisfile.cpp @@ -27,9 +27,11 @@ #include #include +#include #include "vorbisfile.h" + using namespace TagLib; class Vorbis::File::FilePrivate diff --git a/taglib/ogg/xiphcomment.cpp b/taglib/ogg/xiphcomment.cpp index 1d083a71..9072aca4 100644 --- a/taglib/ogg/xiphcomment.cpp +++ b/taglib/ogg/xiphcomment.cpp @@ -27,6 +27,7 @@ #include #include +#include using namespace TagLib; @@ -188,45 +189,42 @@ const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const return d->fieldListMap; } -TagDict Ogg::XiphComment::toDict() const +PropertyMap Ogg::XiphComment::properties() const { return d->fieldListMap; } -void Ogg::XiphComment::fromDict(const TagDict &tagDict) +PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties) { // check which keys are to be deleted StringList toRemove; - FieldListMap::ConstIterator it = d->fieldListMap.begin(); - for(; it != d->fieldListMap.end(); ++it) { - if (!tagDict.contains(it->first)) - toRemove.append(it->first); - } + for(FieldListMap::ConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it) + if (!properties.contains(it->first)) + toRemove.append(it->first); - StringList::ConstIterator removeIt = toRemove.begin(); - for (; removeIt != toRemove.end(); ++removeIt) - removeField(*removeIt); + for(StringList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it) + removeField(*it); - /* now go through keys in tagDict and check that the values match those in the xiph comment */ - TagDict::ConstIterator tagIt = tagDict.begin(); - for (; tagIt != tagDict.end(); ++tagIt) + // now go through keys in \a properties and check that the values match those in the xiph comment */ + PropertyMap::ConstIterator it = properties.begin(); + for(; it != properties.end(); ++it) { - if (!d->fieldListMap.contains(tagIt->first) || !(tagIt->second == d->fieldListMap[tagIt->first])) { - const StringList &sl = tagIt->second; - if(sl.size() == 0) { + if(!d->fieldListMap.contains(it->first) || !(it->second == d->fieldListMap[it->first])) { + const StringList &sl = it->second; + if(sl.size() == 0) // zero size string list -> remove the tag with all values - removeField(tagIt->first); - } + removeField(it->first); else { // replace all strings in the list for the tag StringList::ConstIterator valueIterator = sl.begin(); - addField(tagIt->first, *valueIterator, true); + addField(it->first, *valueIterator, true); ++valueIterator; for(; valueIterator != sl.end(); ++valueIterator) - addField(tagIt->first, *valueIterator, false); + addField(it->first, *valueIterator, false); } } } + return PropertyMap(); } String Ogg::XiphComment::vendorID() const diff --git a/taglib/ogg/xiphcomment.h b/taglib/ogg/xiphcomment.h index f9f23d54..3b44086e 100644 --- a/taglib/ogg/xiphcomment.h +++ b/taglib/ogg/xiphcomment.h @@ -141,19 +141,19 @@ namespace TagLib { const FieldListMap &fieldListMap() const; /*! - * Implements the unified tag dictionary interface -- export function. + * Implements the unified property interface -- export function. * The result is a one-to-one match of the Xiph comment, since it is - * completely compatible with the dictionary interface (in fact, a Xiph + * completely compatible with the property interface (in fact, a Xiph * comment is nothing more than a map from tag names to list of values, * as is the dict interface). */ - TagDict toDict() const; + PropertyMap properties() const; /*! - * Implements the unified tag dictionary interface -- import function. - * The tags from the given dict will be stored one-to-one in the file. + * Implements the unified property interface -- import function. + * The tags from the given map will be stored one-to-one in the file. */ - void fromDict(const TagDict &); + PropertyMap setProperties(const PropertyMap&); /*! * Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 66fb38cb..fc71181f 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -28,10 +28,21 @@ PropertyMap::PropertyMap() : SimplePropertyMap() { } -PropertyMap::PropertyMap(const PropertyMap &m) : SimplePropertyMap(m) +PropertyMap::PropertyMap(const PropertyMap &m) : SimplePropertyMap(m), unsupported(m.unsupported) { } +PropertyMap::PropertyMap(const SimplePropertyMap &m) +{ + for(SimplePropertyMap::ConstIterator it = m.begin(); it != m.end(); ++it){ + String key = prepareKey(it->first); + if(!key.isNull()) + insert(it->first, it->second); + else + unsupported.append(it->first); + } +} + PropertyMap::~PropertyMap() { } diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 071c3e9b..0d2c76af 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -58,6 +58,13 @@ namespace TagLib { PropertyMap(const PropertyMap &m); + /*! + * Creates a PropertyMap initialized from a SimplePropertyMap. Copies all + * entries from \a m that have valid keys. + * Invalid keys will be appended to the unsupportedData() list. + */ + PropertyMap(const SimplePropertyMap &m); + virtual ~PropertyMap(); /*! @@ -93,7 +100,8 @@ namespace TagLib { /*! * Returns true if this map contains all keys of \a other - * and the values coincide for that keys. + * and the values coincide for that keys. Does not take + * the unsupportedData list into account. */ bool contains(const PropertyMap &other) const; @@ -131,6 +139,16 @@ namespace TagLib { */ StringList &operator[](const String &key); + /*! + * Returns true if and only if \other has the same contents as this map. + */ + bool operator==(const PropertyMap &other) const; + + /*! + * Returns false if and only \other has the same contents as this map. + */ + bool operator!=(const PropertyMap &other) const; + /*! * If a PropertyMap is read from a File object using File::properties(), * the StringList returned from this function will represent metadata From de51307de71d510356d1f845f1d5c37a55c1cdb1 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Wed, 15 Feb 2012 21:54:19 +0100 Subject: [PATCH 23/39] Added lots of missing includes --- taglib/ape/apefile.cpp | 1 + taglib/ape/apetag.cpp | 3 ++- taglib/flac/flacfile.cpp | 1 + taglib/it/itfile.cpp | 1 + taglib/mod/modfile.cpp | 1 + taglib/mod/modtag.cpp | 2 ++ taglib/mpc/mpcfile.cpp | 1 + taglib/riff/aiff/aifffile.cpp | 1 + taglib/riff/wav/wavfile.cpp | 1 + taglib/s3m/s3mfile.cpp | 1 + taglib/tag.cpp | 2 ++ taglib/toolkit/tfile.cpp | 1 + taglib/toolkit/tpropertymap.cpp | 2 +- taglib/trueaudio/trueaudiofile.cpp | 1 + taglib/wavpack/wavpackfile.cpp | 1 + taglib/xm/xmfile.cpp | 1 + 16 files changed, 19 insertions(+), 2 deletions(-) diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index 6e806415..c66b55a2 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "apefile.h" diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 5393d72d..8da91ac3 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include "apetag.h" #include "apefooter.h" @@ -251,7 +252,7 @@ PropertyMap APE::Tag::setProperties(const PropertyMap &origProps) } } } - return PropertyMap; + return PropertyMap(); } APE::Footer *APE::Tag::footer() const diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 352ee27e..3ba096f4 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include diff --git a/taglib/it/itfile.cpp b/taglib/it/itfile.cpp index 1c3474cb..dc03b60a 100644 --- a/taglib/it/itfile.cpp +++ b/taglib/it/itfile.cpp @@ -23,6 +23,7 @@ #include "itfile.h" #include "tdebug.h" #include "modfileprivate.h" +#include "tpropertymap.h" using namespace TagLib; using namespace IT; diff --git a/taglib/mod/modfile.cpp b/taglib/mod/modfile.cpp index a05c2137..25fc8715 100644 --- a/taglib/mod/modfile.cpp +++ b/taglib/mod/modfile.cpp @@ -23,6 +23,7 @@ #include "tstringlist.h" #include "tdebug.h" #include "modfileprivate.h" +#include "tpropertymap.h" using namespace TagLib; using namespace Mod; diff --git a/taglib/mod/modtag.cpp b/taglib/mod/modtag.cpp index aaa2f16a..fe6a374c 100644 --- a/taglib/mod/modtag.cpp +++ b/taglib/mod/modtag.cpp @@ -21,6 +21,8 @@ #include "modtag.h" #include "tstringlist.h" +#include "tpropertymap.h" + using namespace TagLib; using namespace Mod; diff --git a/taglib/mpc/mpcfile.cpp b/taglib/mpc/mpcfile.cpp index 6b372a60..ca9471ae 100644 --- a/taglib/mpc/mpcfile.cpp +++ b/taglib/mpc/mpcfile.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "mpcfile.h" #include "id3v1tag.h" diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index bc2a8569..ece84f05 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "aifffile.h" diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index afc307d8..613db4ef 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "wavfile.h" diff --git a/taglib/s3m/s3mfile.cpp b/taglib/s3m/s3mfile.cpp index cf8a7b61..98bc6a56 100644 --- a/taglib/s3m/s3mfile.cpp +++ b/taglib/s3m/s3mfile.cpp @@ -23,6 +23,7 @@ #include "tstringlist.h" #include "tdebug.h" #include "modfileprivate.h" +#include "tpropertymap.h" #include diff --git a/taglib/tag.cpp b/taglib/tag.cpp index 3cebd134..67634081 100644 --- a/taglib/tag.cpp +++ b/taglib/tag.cpp @@ -25,6 +25,8 @@ #include "tag.h" #include "tstringlist.h" +#include "tpropertymap.h" + using namespace TagLib; class Tag::TagPrivate diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 639bc0ec..56a8e19c 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -27,6 +27,7 @@ #include "tfilestream.h" #include "tstring.h" #include "tdebug.h" +#include "tpropertymap.h" #include #include diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index fc71181f..4868f2d0 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -146,7 +146,7 @@ const StringList &PropertyMap::unsupportedData() const return unsupported; } -static String PropertyMap::prepareKey(const String &proposed) { +String PropertyMap::prepareKey(const String &proposed) { if(proposed.isEmpty()) return String::null; for (String::ConstIterator it = proposed.begin(); it != proposed.end(); it++) diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index 346331df..f45c6545 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "trueaudiofile.h" #include "id3v1tag.h" diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index bc0f607a..285d8ef4 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "wavpackfile.h" #include "id3v1tag.h" diff --git a/taglib/xm/xmfile.cpp b/taglib/xm/xmfile.cpp index d4225c45..272e5fe0 100644 --- a/taglib/xm/xmfile.cpp +++ b/taglib/xm/xmfile.cpp @@ -23,6 +23,7 @@ #include "tdebug.h" #include "xmfile.h" #include "modfileprivate.h" +#include "tpropertymap.h" #include #include From cfa5ac6569bb0f3c9ed31f8275cc3ccabcaefbea Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Wed, 15 Feb 2012 21:55:56 +0100 Subject: [PATCH 24/39] Fixed id3v2 test --- tests/test_id3v2.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 1823a5c3..1ac1de8c 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -553,8 +553,7 @@ public: ScopedFileCopy copy("rare_frames", ".mp3"); string newname = copy.fileName(); MPEG::File f(newname.c_str()); - StringList ignored; - TagDict dict = f.ID3v2Tag(false)->toDict(&ignored); + PropertyMap dict = f.ID3v2Tag(false)->properties(); CPPUNIT_ASSERT_EQUAL(uint(6), dict.size()); CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]); CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]); @@ -563,12 +562,12 @@ public: CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"][0]); - CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["USERURL"][0]); + CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["URL:USERURL"][0]); CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"][0]); CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"][0]); - CPPUNIT_ASSERT_EQUAL(String("UFID frame is not supported"), ignored[0]); - CPPUNIT_ASSERT_EQUAL(1u, ignored.size()); + CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size()); + CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front()); } }; From 70c32642790d716d692eae197254c88bc2b869db Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Wed, 15 Feb 2012 22:09:28 +0100 Subject: [PATCH 25/39] fixed tests --- tests/test_apetag.cpp | 5 +++-- tests/test_flac.cpp | 7 ++++--- tests/test_id3v2.cpp | 1 + tests/test_ogg.cpp | 17 +++++++++-------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index fc38907f..8419d9ce 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "utils.h" @@ -39,12 +40,12 @@ public: void testDict() { APE::Tag tag; - TagDict dict = tag.toDict(); + PropertyMap dict = tag.properties(); CPPUNIT_ASSERT(dict.isEmpty()); dict["ARTIST"] = String("artist 1"); dict["ARTIST"].append("artist 2"); dict["TRACKNUMBER"].append("17"); - tag.fromDict(dict); + tag.setProperties(dict); CPPUNIT_ASSERT_EQUAL(String("17"), tag.itemListMap()["TRACK"].values()[0]); CPPUNIT_ASSERT_EQUAL(2u, tag.itemListMap()["ARTIST"].values().size()); CPPUNIT_ASSERT_EQUAL(String("artist 1"), tag.artist()); diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index f9a0afa4..1bf6015a 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include "utils.h" @@ -216,15 +217,15 @@ public: string newname = copy.fileName(); FLAC::File *f = new FLAC::File(newname.c_str()); - TagDict dict; + PropertyMap dict; dict["ARTIST"].append("artøst 1"); dict["ARTIST"].append("artöst 2"); - f->fromDict(dict); + f->setProperties(dict); f->save(); delete f; f = new FLAC::File(newname.c_str()); - dict = f->toDict(); + dict = f->properties(); CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), dict["ARTIST"].size()); CPPUNIT_ASSERT_EQUAL(String("artøst 1"), dict["ARTIST"][0]); CPPUNIT_ASSERT_EQUAL(String("artöst 2"), dict["ARTIST"][1]); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 1ac1de8c..ee304fbb 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "utils.h" using namespace std; diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index de25d3ed..b5c6b557 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -60,15 +61,15 @@ public: Vorbis::File *f = new Vorbis::File(newname.c_str()); - CPPUNIT_ASSERT_EQUAL(uint(0), f->tag()->toDict().size()); + CPPUNIT_ASSERT_EQUAL(uint(0), f->tag()->properties().size()); - TagDict newTags; + PropertyMap newTags; StringList values("value 1"); values.append("value 2"); newTags["ARTIST"] = values; - f->tag()->fromDict(newTags); + f->tag()->setProperties(newTags); - TagDict map = f->tag()->toDict(); + PropertyMap map = f->tag()->properties(); CPPUNIT_ASSERT_EQUAL(uint(1), map.size()); CPPUNIT_ASSERT_EQUAL(uint(2), map["ARTIST"].size()); CPPUNIT_ASSERT_EQUAL(String("value 1"), map["ARTIST"][0]); @@ -82,7 +83,7 @@ public: string newname = copy.fileName(); Vorbis::File *f = new Vorbis::File(newname.c_str()); - TagDict tags = f->tag()->toDict(); + PropertyMap tags = f->tag()->properties(); CPPUNIT_ASSERT_EQUAL(uint(2), tags["UNUSUALTAG"].size()); CPPUNIT_ASSERT_EQUAL(String("usual value"), tags["UNUSUALTAG"][0]); @@ -91,9 +92,9 @@ public: tags["UNICODETAG"][0] = L"νεω ναλυε"; tags.erase("UNUSUALTAG"); - f->tag()->fromDict(tags); - CPPUNIT_ASSERT_EQUAL(String(L"νεω ναλυε"), f->tag()->toDict()["UNICODETAG"][0]); - CPPUNIT_ASSERT_EQUAL(false, f->tag()->toDict().contains("UNUSUALTAG")); + f->tag()->setProperties(tags); + CPPUNIT_ASSERT_EQUAL(String(L"νεω ναλυε"), f->tag()->properties()["UNICODETAG"][0]); + CPPUNIT_ASSERT_EQUAL(false, f->tag()->properties().contains("UNUSUALTAG")); delete f; } From 6c054af3ed45e7d227c56844d1e1dfd4e81e77d9 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 19 Feb 2012 12:15:28 +0100 Subject: [PATCH 26/39] Added some functions, started to fix bugs. --- .../id3v2/frames/textidentificationframe.cpp | 16 +++++-- .../id3v2/frames/textidentificationframe.h | 4 +- taglib/mpeg/id3v2/frames/urllinkframe.cpp | 12 +++++ taglib/mpeg/id3v2/frames/urllinkframe.h | 6 +++ taglib/mpeg/id3v2/id3v2tag.cpp | 35 +++++++++++++- taglib/toolkit/tlist.h | 5 ++ taglib/toolkit/tlist.tcc | 6 +++ taglib/toolkit/tpropertymap.cpp | 47 ++++++++++++++++--- 8 files changed, 118 insertions(+), 13 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 6a9d760b..d1336d22 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -25,7 +25,7 @@ #include #include - +#include #include "textidentificationframe.h" #include "tpropertymap.h" #include "id3v1genres.h" @@ -321,6 +321,14 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(const ByteVector &data) checkFields(); } +UserTextIdentificationFrame::UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding) : + TextIdentificationFrame("TXXX", encoding), + d(0) +{ + setDescription(description); + setText(values); +} + String UserTextIdentificationFrame::toString() const { return "[" + description() + "] " + fieldList().toString(); @@ -378,10 +386,12 @@ PropertyMap UserTextIdentificationFrame::asProperties() const 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(L"TXXX/" + description()); - else - for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) + else { + StringList v = fieldList(); + for(StringList::ConstIterator it = v.begin(); it != v.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 b82e7d3a..283f0c72 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.h +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.h @@ -253,9 +253,9 @@ namespace TagLib { /*! * Creates a user defined text identification frame with the given \a description - * and \a text. + * and \a values. */ - UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding = String::Latin1); + UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding = String::UTF8); virtual String toString() const; diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/taglib/mpeg/id3v2/frames/urllinkframe.cpp index db872db2..f06a04dd 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -26,6 +26,7 @@ ***************************************************************************/ #include "urllinkframe.h" +#include "id3v2tag.h" #include #include #include @@ -167,6 +168,17 @@ PropertyMap UserUrlLinkFrame::asProperties() const return map; } +UserUrlLinkFrame *UserUrlLinkFrame::find(ID3v2::Tag *tag, const String &description) // static +{ + FrameList l = tag->frameList("WXXX"); + for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) { + UserUrlLinkFrame *f = dynamic_cast(*it); + if(f && f->description() == description) + return f; + } + return 0; +} + 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 4eea36b3..7ac966b2 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.h +++ b/taglib/mpeg/id3v2/frames/urllinkframe.h @@ -161,6 +161,12 @@ namespace TagLib { */ PropertyMap asProperties() const; + /*! + * Searches for the user defined url frame with the description \a description + * in \a tag. This returns null if no matching frames were found. + */ + static UserUrlLinkFrame *find(Tag *tag, const String &description); + protected: virtual void parseFields(const ByteVector &data); virtual ByteVector renderFields() const; diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 05d58f48..e6be1f91 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -337,11 +337,42 @@ void ID3v2::Tag::removeFrames(const ByteVector &id) PropertyMap ID3v2::Tag::properties() const { PropertyMap properties; - for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) - properties.merge((*it)->asProperties()); + + for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) { + PropertyMap props = (*it)->asProperties(); + debug(props); + properties.merge(props); + } return properties; } +void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) +{ + // entries of unsupportedData() are usually frame IDs which are not supported + // by the PropertyMap interface. Three special cases exist: TXXX, WXXX, and COMM + // frames may also be unsupported if their description() is not a valid key. + for(StringList::ConstIterator it = properties.begin(); it != properties.end(); ++it){ + ByteVector id = it->substr(0,4).data(String::Latin1); + if(id == "TXXX") { + String description = it->substr(5); + Frame *frame = UserTextIdentificationFrame::find(this, description); + if(frame) + removeFrame(frame); + } else if(id == "WXXX") { + String description = it->substr(5); + Frame *frame = UserUrlLinkFrame::find(this, description); + if(frame) + removeFrame(frame); + } else if(id == "COMM") { + String description = it->substr(5); + Frame *frame = CommentsFrame::findByDescription(this, description); + if(frame) + removeFrame(frame); + } else + removeFrames(id); // there should be only one frame with "id" + } +} + PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps) { FrameList framesToDelete; diff --git a/taglib/toolkit/tlist.h b/taglib/toolkit/tlist.h index dce0e1c6..0099dad5 100644 --- a/taglib/toolkit/tlist.h +++ b/taglib/toolkit/tlist.h @@ -227,6 +227,11 @@ namespace TagLib { */ bool operator==(const List &l) const; + /*! + * Compares this list with \a l and returns true if the lists differ. + */ + bool operator!=(const List &l) const; + protected: /* * If this List is being shared via implicit sharing, do a deep copy of the diff --git a/taglib/toolkit/tlist.tcc b/taglib/toolkit/tlist.tcc index a11887d8..37817f05 100644 --- a/taglib/toolkit/tlist.tcc +++ b/taglib/toolkit/tlist.tcc @@ -300,6 +300,12 @@ bool List::operator==(const List &l) const return d->list == l.d->list; } +template +bool List::operator!=(const List &l) const +{ + return d->list != l.d->list; +} + //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 4868f2d0..8dab7756 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -20,7 +20,6 @@ ***************************************************************************/ #include "tpropertymap.h" - using namespace TagLib; @@ -90,18 +89,34 @@ PropertyMap::ConstIterator PropertyMap::find(const String &key) const bool PropertyMap::contains(const String &key) const { String realKey = prepareKey(key); - // we consider keys with empty value list as not present - if(realKey.isNull() || SimplePropertyMap::operator[](realKey).isEmpty()) + if(realKey.isNull()) return false; return SimplePropertyMap::contains(realKey); } +bool PropertyMap::contains(const PropertyMap &other) const +{ + for(ConstIterator it = other.begin(); it != other.end(); ++it) { + if(!SimplePropertyMap::contains(it->first)) + return false; + if ((*this)[it->first] != it->second) + return false; + } + return true; +} + PropertyMap &PropertyMap::erase(const String &key) { String realKey = prepareKey(key); - if(realKey.isNull()) - return *this; - SimplePropertyMap::erase(realKey); + if(!realKey.isNull()) + SimplePropertyMap::erase(realKey); + return *this; +} + +PropertyMap &PropertyMap::erase(const PropertyMap &other) +{ + for(ConstIterator it = other.begin(); it != other.end(); ++it) + erase(it->first); return *this; } @@ -126,6 +141,26 @@ StringList &PropertyMap::operator[](const String &key) return SimplePropertyMap::operator[](realKey); } +bool PropertyMap::operator==(const PropertyMap &other) const +{ + for(ConstIterator it = other.begin(); it != other.end(); ++it) { + ConstIterator thisFind = find(it->first); + if( thisFind == end() || (thisFind->second != it->second) ) + return false; + } + for(ConstIterator it = begin(); it != end(); ++it) { + ConstIterator otherFind = other.find(it->first); + if( otherFind == other.end() || (otherFind->second != it->second) ) + return false; + } + return unsupported == other.unsupported; +} + +bool PropertyMap::operator!=(const PropertyMap &other) const +{ + return !(*this == other); +} + void PropertyMap::removeEmpty() { StringList emptyKeys; From 23d303a8962fa61ffad71df65b569091908c98a0 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 19 Feb 2012 15:13:31 +0100 Subject: [PATCH 27/39] fixed bugs preventing tests from running --- taglib/mpeg/id3v2/frames/commentsframe.cpp | 6 +++--- taglib/mpeg/id3v2/frames/urllinkframe.cpp | 10 ++++------ taglib/mpeg/id3v2/id3v2tag.cpp | 2 -- taglib/toolkit/tpropertymap.cpp | 8 ++++++++ taglib/toolkit/tpropertymap.h | 2 ++ tests/test_id3v2.cpp | 11 +++++++---- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/commentsframe.cpp b/taglib/mpeg/id3v2/frames/commentsframe.cpp index 2b59db4d..2c6c49f9 100644 --- a/taglib/mpeg/id3v2/frames/commentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/commentsframe.cpp @@ -114,9 +114,9 @@ PropertyMap CommentsFrame::asProperties() const { String key = PropertyMap::prepareKey(description()); PropertyMap map; - if(key.isEmpty()) - key = "COMMENT"; - if(key.isNull()) + if(key.isEmpty() || key == "COMMENT") + map.insert("COMMENT", text()); + else if(key.isNull()) map.unsupportedData().append(L"COMM/" + description()); else map.insert("COMMENT:" + key, text()); diff --git a/taglib/mpeg/id3v2/frames/urllinkframe.cpp b/taglib/mpeg/id3v2/frames/urllinkframe.cpp index f06a04dd..5e4f2db7 100644 --- a/taglib/mpeg/id3v2/frames/urllinkframe.cpp +++ b/taglib/mpeg/id3v2/frames/urllinkframe.cpp @@ -156,12 +156,10 @@ void UserUrlLinkFrame::setDescription(const String &s) PropertyMap UserUrlLinkFrame::asProperties() const { PropertyMap map; - String key; - if(description().isEmpty()) - key = "URL"; - else - key = PropertyMap::prepareKey(description()); - if(key.isNull()) + String key = PropertyMap::prepareKey(description()); + if(key.isEmpty() || key.upper() == "URL") + map.insert("URL", url()); + else if(key.isNull()) map.unsupportedData().append(L"WXXX/" + description()); else map.insert("URL:" + key, url()); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index e6be1f91..4085daae 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -337,10 +337,8 @@ void ID3v2::Tag::removeFrames(const ByteVector &id) PropertyMap ID3v2::Tag::properties() const { PropertyMap properties; - for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) { PropertyMap props = (*it)->asProperties(); - debug(props); properties.merge(props); } return properties; diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 8dab7756..40bcaba7 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -161,6 +161,14 @@ bool PropertyMap::operator!=(const PropertyMap &other) const return !(*this == other); } +String PropertyMap::toString() const +{ + String ret = ""; + for(ConstIterator it = begin(); it != end(); ++it) + ret += it->first+"="+it->second.toString(", ") + "\n"; + return ret; +} + void PropertyMap::removeEmpty() { StringList emptyKeys; diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 0d2c76af..b955739b 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -166,6 +166,8 @@ namespace TagLib { */ void removeEmpty(); + String toString() const; + /*! * Converts \a proposed into another String suitable to be used as * a key, or returns String::null if this is not possible. diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index ee304fbb..8de91707 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -561,12 +561,15 @@ public: CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION2"][0]); CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION2"][1]); - CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"][0]); + CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"].front()); - CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["URL:USERURL"][0]); - CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"][0]); + CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["URL:USERURL"].front()); + + CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"].front()); + debug(dict.toString()); + CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front()); + debug("565"); - CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"][0]); CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size()); CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front()); } From 495a028da3493e07dbc8049c82048b706eff1197 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 19 Feb 2012 15:14:59 +0100 Subject: [PATCH 28/39] removed debug messages --- taglib/mpeg/id3v2/frames/textidentificationframe.cpp | 1 - tests/test_id3v2.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index d1336d22..3c3d4376 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -25,7 +25,6 @@ #include #include -#include #include "textidentificationframe.h" #include "tpropertymap.h" #include "id3v1genres.h" diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 8de91707..ef92b44d 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -566,9 +566,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["URL:USERURL"].front()); CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"].front()); - debug(dict.toString()); CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front()); - debug("565"); CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size()); CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front()); From d28cc83fb4aa0d8ab980187969fed348b8e896b4 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 25 Feb 2012 18:22:17 +0100 Subject: [PATCH 29/39] Added another test for ID3v2 PropertyMap interface; fixed various bugs --- .../id3v2/frames/textidentificationframe.cpp | 6 +- .../frames/unsynchronizedlyricsframe.cpp | 8 +-- taglib/mpeg/id3v2/id3v2tag.cpp | 1 + taglib/toolkit/tpropertymap.cpp | 2 + tests/test_id3v2.cpp | 55 ++++++++++++++++++- 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 3c3d4376..0ab1c84c 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -260,7 +260,8 @@ PropertyMap TextIdentificationFrame::makeTIPLProperties() const map.unsupportedData().append(frameID()); return map; } - for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) { + StringList l = fieldList(); + for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) { bool found = false; for(uint i = 0; i < involvedPeopleSize; ++i) if(*it == involvedPeople[i][0]) { @@ -286,7 +287,8 @@ PropertyMap TextIdentificationFrame::makeTMCLProperties() const map.unsupportedData().append(frameID()); return map; } - for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) { + StringList l = fieldList(); + for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) { String instrument = PropertyMap::prepareKey(*it); if(instrument.isNull()) { // instrument is not a valid key -> frame unsupported diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index ae013ec9..6719a9b3 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -114,11 +114,11 @@ void UnsynchronizedLyricsFrame::setTextEncoding(String::Type encoding) PropertyMap UnsynchronizedLyricsFrame::asProperties() const { - String key = PropertyMap::prepareKey(description()); PropertyMap map; - if(key.isEmpty()) - key = "LYRICS"; - if(key.isNull()) + String key = PropertyMap::prepareKey(description()); + if(key.isEmpty() || key.upper() == "LYRICS") + map.insert("LYRICS", text()); + else if(key.isNull()) map.unsupportedData().append(L"USLT/" + description()); else map.insert("LYRICS:" + key, text()); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 4085daae..cdfa4353 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -339,6 +339,7 @@ PropertyMap ID3v2::Tag::properties() const PropertyMap properties; for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) { PropertyMap props = (*it)->asProperties(); + debug("read properties:\n" + props.toString()); properties.merge(props); } return properties; diff --git a/taglib/toolkit/tpropertymap.cpp b/taglib/toolkit/tpropertymap.cpp index 40bcaba7..1743a0b2 100644 --- a/taglib/toolkit/tpropertymap.cpp +++ b/taglib/toolkit/tpropertymap.cpp @@ -166,6 +166,8 @@ String PropertyMap::toString() const String ret = ""; for(ConstIterator it = begin(); it != end(); ++it) ret += it->first+"="+it->second.toString(", ") + "\n"; + if(!unsupported.isEmpty()) + ret += "Unsupported Data: " + unsupported.toString(", ") + "\n"; return ret; } diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index ef92b44d..530b3f3d 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +69,8 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testDowngradeTo23); // CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together CPPUNIT_TEST(testCompressedFrameWithBrokenLength); - CPPUNIT_TEST(testDictInterface); + CPPUNIT_TEST(testPropertyInterface); + CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST_SUITE_END(); public: @@ -549,7 +551,7 @@ public: CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->picture().size()); } - void testDictInterface() + void testPropertyInterface() { ScopedFileCopy copy("rare_frames", ".mp3"); string newname = copy.fileName(); @@ -572,6 +574,55 @@ public: CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front()); } + void testPropertyInterface2() + { + ID3v2::Tag tag; + ID3v2::UnsynchronizedLyricsFrame *frame1 = new ID3v2::UnsynchronizedLyricsFrame(); + frame1->setDescription("test"); + frame1->setText("la-la-la test"); + tag.addFrame(frame1); + + ID3v2::UnsynchronizedLyricsFrame *frame2 = new ID3v2::UnsynchronizedLyricsFrame(); + frame2->setDescription(""); + frame2->setText("la-la-la nodescription"); + tag.addFrame(frame2); + + ID3v2::AttachedPictureFrame *frame3 = new ID3v2::AttachedPictureFrame(); + frame3->setDescription("test picture"); + tag.addFrame(frame3); + + ID3v2::TextIdentificationFrame *frame4 = new ID3v2::TextIdentificationFrame("TIPL"); + frame4->setText("single value is invalid for TIPL"); + tag.addFrame(frame4); + + ID3v2::TextIdentificationFrame *frame5 = new ID3v2::TextIdentificationFrame("TMCL"); + StringList tmclData; + tmclData.append("VIOLIN"); + tmclData.append("a violinist"); + tmclData.append("PIANO"); + tmclData.append("a pianist"); + frame5->setText(tmclData); + tag.addFrame(frame5); + + PropertyMap properties = tag.properties(); + + CPPUNIT_ASSERT_EQUAL(2u, properties.unsupportedData().size()); + CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL")); + CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC")); + + CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN")); + CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO")); + CPPUNIT_ASSERT_EQUAL(String("a violinist"), properties["PERFORMER:VIOLIN"].front()); + CPPUNIT_ASSERT_EQUAL(String("a pianist"), properties["PERFORMER:PIANO"].front()); + + CPPUNIT_ASSERT(properties.contains("LYRICS")); + CPPUNIT_ASSERT(properties.contains("LYRICS:TEST")); + + tag.removeUnsupportedProperties(properties.unsupportedData()); + CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty()); + CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); From 05b5e0692898e0904b4106b61882b459acda08e4 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 25 Feb 2012 18:32:00 +0100 Subject: [PATCH 30/39] added APE tag PropertyMap test --- tests/test_ape.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_ape.cpp b/tests/test_ape.cpp index c95ff0c2..e6c31405 100644 --- a/tests/test_ape.cpp +++ b/tests/test_ape.cpp @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include #include "utils.h" using namespace std; @@ -16,6 +19,7 @@ class TestAPE : public CppUnit::TestFixture CPPUNIT_TEST(testProperties399); CPPUNIT_TEST(testProperties396); CPPUNIT_TEST(testProperties390); + CPPUNIT_TEST(testPropertyinterface); CPPUNIT_TEST_SUITE_END(); public: @@ -47,6 +51,27 @@ public: CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); } + void testPropertyinterface() + { + APE::Tag tag; + APE::Item item1 = APE::Item("TRACK", "17"); + tag.setItem("TRACK", item1); + + APE::Item item2 = APE::Item(); + item2.setType(APE::Item::Binary); + tag.setItem("TESTBINARY", item2); + + PropertyMap properties = tag.properties(); + CPPUNIT_ASSERT_EQUAL(1u, properties.unsupportedData().size()); + CPPUNIT_ASSERT(properties.contains("TRACKNUMBER")); + CPPUNIT_ASSERT(!properties.contains("TRACK")); + CPPUNIT_ASSERT(tag.itemListMap().contains("TESTBINARY")); + + tag.removeUnsupportedProperties(properties.unsupportedData()); + CPPUNIT_ASSERT(!tag.itemListMap().contains("TESTBINARY")); + + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestAPE); From b8d5246f8812a5d7aaf77827c49d7ca488782ba5 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 25 Feb 2012 18:46:19 +0100 Subject: [PATCH 31/39] Moved APE test to correct place; added MOD tag test. --- tests/test_ape.cpp | 25 ------------------------- tests/test_apetag.cpp | 35 +++++++++++++++++++++++++++++++++-- tests/test_mod.cpp | 18 ++++++++++++++++++ 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/tests/test_ape.cpp b/tests/test_ape.cpp index e6c31405..c95ff0c2 100644 --- a/tests/test_ape.cpp +++ b/tests/test_ape.cpp @@ -5,9 +5,6 @@ #include #include #include -#include -#include -#include #include "utils.h" using namespace std; @@ -19,7 +16,6 @@ class TestAPE : public CppUnit::TestFixture CPPUNIT_TEST(testProperties399); CPPUNIT_TEST(testProperties396); CPPUNIT_TEST(testProperties390); - CPPUNIT_TEST(testPropertyinterface); CPPUNIT_TEST_SUITE_END(); public: @@ -51,27 +47,6 @@ public: CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); } - void testPropertyinterface() - { - APE::Tag tag; - APE::Item item1 = APE::Item("TRACK", "17"); - tag.setItem("TRACK", item1); - - APE::Item item2 = APE::Item(); - item2.setType(APE::Item::Binary); - tag.setItem("TESTBINARY", item2); - - PropertyMap properties = tag.properties(); - CPPUNIT_ASSERT_EQUAL(1u, properties.unsupportedData().size()); - CPPUNIT_ASSERT(properties.contains("TRACKNUMBER")); - CPPUNIT_ASSERT(!properties.contains("TRACK")); - CPPUNIT_ASSERT(tag.itemListMap().contains("TESTBINARY")); - - tag.removeUnsupportedProperties(properties.unsupportedData()); - CPPUNIT_ASSERT(!tag.itemListMap().contains("TESTBINARY")); - - } - }; CPPUNIT_TEST_SUITE_REGISTRATION(TestAPE); diff --git a/tests/test_apetag.cpp b/tests/test_apetag.cpp index 8419d9ce..422725df 100644 --- a/tests/test_apetag.cpp +++ b/tests/test_apetag.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "utils.h" using namespace std; @@ -16,7 +17,8 @@ class TestAPETag : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestAPETag); CPPUNIT_TEST(testIsEmpty); CPPUNIT_TEST(testIsEmpty2); - CPPUNIT_TEST(testDict); + CPPUNIT_TEST(testPropertyInterface1); + CPPUNIT_TEST(testPropertyInterface2); CPPUNIT_TEST_SUITE_END(); public: @@ -37,7 +39,7 @@ public: CPPUNIT_ASSERT(!tag.isEmpty()); } - void testDict() + void testPropertyInterface1() { APE::Tag tag; PropertyMap dict = tag.properties(); @@ -52,6 +54,35 @@ public: CPPUNIT_ASSERT_EQUAL(17u, tag.track()); } + void testPropertyInterface2() + { + APE::Tag tag; + APE::Item item1 = APE::Item("TRACK", "17"); + tag.setItem("TRACK", item1); + + APE::Item item2 = APE::Item(); + item2.setType(APE::Item::Binary); + tag.setItem("TESTBINARY", item2); + + PropertyMap properties = tag.properties(); + CPPUNIT_ASSERT_EQUAL(1u, properties.unsupportedData().size()); + CPPUNIT_ASSERT(properties.contains("TRACKNUMBER")); + CPPUNIT_ASSERT(!properties.contains("TRACK")); + CPPUNIT_ASSERT(tag.itemListMap().contains("TESTBINARY")); + + tag.removeUnsupportedProperties(properties.unsupportedData()); + CPPUNIT_ASSERT(!tag.itemListMap().contains("TESTBINARY")); + + APE::Item item3 = APE::Item("TRACKNUMBER", "29"); + tag.setItem("TRACKNUMBER", item3); + properties = tag.properties(); + CPPUNIT_ASSERT_EQUAL(2u, properties["TRACKNUMBER"].size()); + CPPUNIT_ASSERT_EQUAL(String("17"), properties["TRACKNUMBER"][0]); + CPPUNIT_ASSERT_EQUAL(String("29"), properties["TRACKNUMBER"][1]); + + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestAPETag); + diff --git a/tests/test_mod.cpp b/tests/test_mod.cpp index 67c46f28..a3919d7e 100644 --- a/tests/test_mod.cpp +++ b/tests/test_mod.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "utils.h" using namespace std; @@ -51,6 +52,7 @@ class TestMod : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestMod); CPPUNIT_TEST(testReadTags); CPPUNIT_TEST(testWriteTags); + CPPUNIT_TEST(testPropertyInterface); CPPUNIT_TEST_SUITE_END(); public: @@ -75,6 +77,22 @@ public: TEST_FILE_PATH_C("changed.mod"))); } + void testPropertyInterface() + { + Mod::Tag t; + PropertyMap properties; + properties["BLA"] = String("bla"); + properties["ARTIST"] = String("artist1"); + properties["ARTIST"].append("artist2"); + properties["TITLE"] = String("title"); + + PropertyMap unsupported = t.setProperties(properties); + CPPUNIT_ASSERT(unsupported.contains("BLA")); + CPPUNIT_ASSERT(unsupported.contains("ARTIST")); + CPPUNIT_ASSERT_EQUAL(properties["ARTIST"], unsupported["ARTIST"]); + CPPUNIT_ASSERT(!unsupported.contains("TITLE")); + } + private: void testRead(FileName fileName, const String &title, const String &comment) { From 79670beca13514be767cf9e3e43d9da94498572d Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 25 Feb 2012 18:59:53 +0100 Subject: [PATCH 32/39] some cosmetic changes --- .../id3v2/frames/textidentificationframe.cpp | 4 +- .../id3v2/frames/unsynchronizedlyricsframe.h | 7 +- taglib/toolkit/tfile.cpp | 86 +++++++++---------- taglib/trueaudio/trueaudiofile.cpp | 8 +- taglib/wavpack/wavpackfile.cpp | 8 +- 5 files changed, 54 insertions(+), 59 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 0ab1c84c..d68d0dfe 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -161,11 +161,11 @@ PropertyMap TextIdentificationFrame::asProperties() const *it = ID3v1::genre(test); } } else if(tagName == "DATE") { - for (StringList::Iterator it = values.begin(); it != values.end(); ++it) { + 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) + if(tpos != -1) (*it)[tpos] = ' '; } } diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h index 03648ee4..2a3c5f9a 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h @@ -135,12 +135,7 @@ 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. - */ - /*! - * Parses this frame as PropertyMap with a single key. + /*! Parses this frame as PropertyMap with a single key. * - if description() is empty or "LYRICS", the key will be "LYRICS" * - if description() is not a valid PropertyMap key, the frame will be * marked unsupported by an entry "USLT/" in the unsupportedData() diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 56a8e19c..09b8fdbc 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -117,35 +117,35 @@ FileName File::name() const PropertyMap File::properties() const { // ugly workaround until this method is virtual - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->properties(); // no specialized implementation available -> use generic one // - ASF: ugly format, largely undocumented, not worth implementing @@ -160,31 +160,31 @@ void File::removeUnsupportedProperties(const StringList &properties) { // here we only consider those formats that could possibly contain // unsupported properties - if (dynamic_cast(this)) + if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); else tag()->removeUnsupportedProperties(properties); @@ -192,35 +192,35 @@ void File::removeUnsupportedProperties(const StringList &properties) PropertyMap File::setProperties(const PropertyMap &properties) { - if (dynamic_cast(this)) + if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); - else if (dynamic_cast(this)) + else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); else return tag()->setProperties(properties); diff --git a/taglib/trueaudio/trueaudiofile.cpp b/taglib/trueaudio/trueaudiofile.cpp index f45c6545..e10f6fa5 100644 --- a/taglib/trueaudio/trueaudiofile.cpp +++ b/taglib/trueaudio/trueaudiofile.cpp @@ -132,18 +132,18 @@ PropertyMap TrueAudio::File::properties() const { // once Tag::properties() is virtual, this case distinction could actually be done // within TagUnion. - if (d->hasID3v2) + if(d->hasID3v2) return d->tag.access(ID3v2Index, false)->properties(); - if (d->hasID3v1) + if(d->hasID3v1) return d->tag.access(ID3v1Index, false)->properties(); return PropertyMap(); } PropertyMap TrueAudio::File::setProperties(const PropertyMap &properties) { - if (d->hasID3v2) + if(d->hasID3v2) return d->tag.access(ID3v2Index, false)->setProperties(properties); - else if (d->hasID3v1) + else if(d->hasID3v1) return d->tag.access(ID3v1Index, false)->setProperties(properties); else return d->tag.access(ID3v2Index, true)->setProperties(properties); diff --git a/taglib/wavpack/wavpackfile.cpp b/taglib/wavpack/wavpackfile.cpp index 285d8ef4..2addabae 100644 --- a/taglib/wavpack/wavpackfile.cpp +++ b/taglib/wavpack/wavpackfile.cpp @@ -108,18 +108,18 @@ TagLib::Tag *WavPack::File::tag() const PropertyMap WavPack::File::properties() const { - if (d->hasAPE) + if(d->hasAPE) return d->tag.access(APEIndex, false)->properties(); - if (d->hasID3v1) + if(d->hasID3v1) return d->tag.access(ID3v1Index, false)->properties(); return PropertyMap(); } PropertyMap WavPack::File::setProperties(const PropertyMap &properties) { - if (d->hasAPE) + if(d->hasAPE) return d->tag.access(APEIndex, false)->setProperties(properties); - else if (d->hasID3v1) + else if(d->hasID3v1) return d->tag.access(ID3v1Index, false)->setProperties(properties); else return d->tag.access(APE, true)->setProperties(properties); From b05c3161c7cbb1010a7f8c17fda29c4f2cb36b9d Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 25 Feb 2012 19:11:31 +0100 Subject: [PATCH 33/39] Added ID3v2 PropertyMap interface documentation. --- taglib/mpeg/id3v2/id3v2tag.cpp | 3 +-- taglib/mpeg/id3v2/id3v2tag.h | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index cdfa4353..4731ba55 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -24,7 +24,6 @@ ***************************************************************************/ #include -#include #include "id3v2tag.h" #include "id3v2header.h" @@ -34,6 +33,7 @@ #include "tbytevector.h" #include "id3v1genres.h" #include "tpropertymap.h" +#include #include "frames/textidentificationframe.h" #include "frames/commentsframe.h" @@ -339,7 +339,6 @@ PropertyMap ID3v2::Tag::properties() const PropertyMap properties; for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) { PropertyMap props = (*it)->asProperties(); - debug("read properties:\n" + props.toString()); properties.merge(props); } return properties; diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 56c055a3..3728868a 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -263,7 +263,30 @@ namespace TagLib { /*! * 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 PropertyMap. + * frame types into a free-form string-to-stringlist PropertyMap: + * - if ID3v2 frame ID is known by Frame::frameIDToKey(), the returned + * key is used + * - if the frame ID is "TXXX" (user text frame), the description() is + * used as key + * - if the frame ID is "WXXX" (user url frame), + * - if the description is empty or "URL", the key "URL" is used + * - otherwise, the key "URL:" is used; + * - if the frame ID is "COMM" (comments frame), + * - if the description is empty or "COMMENT", the key "COMMENT" + * is used + * - otherwise, the key "COMMENT:" is used; + * - if the frame ID is "USLT" (unsynchronized lyrics), + * - if the description is empty or "LYRICS", the key "LYRICS" is used + * - otherwise, the key "LYRICS:" is used; + * - if the frame ID is "TIPL" (involved peoples list), and if all the + * roles defined in the frame are known in TextIdentificationFrame::involvedPeopleMap(), + * then "=" will be contained in the returned obejct for each + * - if the frame ID is "TMCL" (musician credit list), then + * "PERFORMER:=" will be contained in the returned + * PropertyMap for each defined musician + * In any other case, the unsupportedData() of the returned object will contain + * the frame's ID and, in case of a frame ID which is allowed to appear more than + * once, the description, separated by a "/". * */ PropertyMap properties() const; From fa0656e3c6f268e673e2806d57bad47ec48d91b1 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 26 Feb 2012 10:37:59 +0100 Subject: [PATCH 34/39] remove Tests/Examples build from CMakeLists --- CMakeLists.txt | 4 ++-- taglib/ape/apefile.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6976634..0c0c9097 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,8 @@ if(VISIBILITY_HIDDEN) add_definitions (-fvisibility=hidden) endif() -option(BUILD_TESTS "Build the test suite" ON) -option(BUILD_EXAMPLES "Build the examples" ON) +option(BUILD_TESTS "Build the test suite" OFF) +option(BUILD_EXAMPLES "Build the examples" OFF) option(NO_ITUNES_HACKS "Disable workarounds for iTunes bugs" OFF) diff --git a/taglib/ape/apefile.cpp b/taglib/ape/apefile.cpp index c66b55a2..44f459e9 100644 --- a/taglib/ape/apefile.cpp +++ b/taglib/ape/apefile.cpp @@ -134,7 +134,7 @@ PropertyMap APE::File::setProperties(const PropertyMap &properties) else if(d->hasID3v1) return d->tag.access(ID3v1Index, false)->setProperties(properties); else - return d->tag.access(APE, true)->setProperties(properties); + return d->tag.access(APEIndex, true)->setProperties(properties); } APE::Properties *APE::File::audioProperties() const From 0a3b998ca598ae7c36d719c613163ae634fd0555 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 26 Feb 2012 10:43:08 +0100 Subject: [PATCH 35/39] Fix USLT frame creation in Frame::createTextualFrame() --- taglib/mpeg/id3v2/id3v2frame.cpp | 4 +++- taglib/mpeg/id3v2/id3v2frame.h | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 16b02d55..be566ca1 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -102,6 +102,7 @@ ByteVector Frame::textDelimiter(String::Type t) const String Frame::instrumentPrefix("PERFORMER:"); const String Frame::commentPrefix("COMMENT:"); +const String Frame::lyricsPrefix("LYRICS:"); const String Frame::urlPrefix("URL:"); //////////////////////////////////////////////////////////////////////////////// @@ -125,8 +126,9 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // } // now we check if it's one of the "special" cases: // -LYRICS: depending on the number of values, use USLT or TXXX (with description=LYRICS) - if(key == "LYRICS" && values.size() == 1){ + if((key == "LYRICS" || key.startsWith(lyricsPrefix)) && values.size() == 1){ UnsynchronizedLyricsFrame *frame = new UnsynchronizedLyricsFrame(); + frame->setDescription(key == "LYRICS" ? key : key.substr(lyricsPrefix.size())); frame->setText(values.front()); return frame; } diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 3a9ade26..95c4070b 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -146,6 +146,11 @@ namespace TagLib { * frame for a non-standard key. In the current implementation, this is "COMMENT:". */ static const String commentPrefix; + /*! + * The PropertyMap key prefix which triggers the use of a USLT frame instead of a TXXX + * frame for a non-standard key. In the current implementation, this is "LYRICS:". + */ + static const String lyricsPrefix; /*! * The PropertyMap key prefix which triggers the use of a WXXX frame instead of a TXX * frame for a non-standard key. In the current implementation, this is "URL:". From 37c87e0317fa1617ee7a10bc89d05c44ccce42b7 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 26 Feb 2012 10:56:18 +0100 Subject: [PATCH 36/39] Fixed identation --- taglib/mpeg/id3v2/id3v2frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index be566ca1..7a7c0da1 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -382,9 +382,9 @@ static const char *frameTranslation[][2] = { Map &idMap() { static Map m; - if(m.isEmpty()) - for(size_t i = 0; i < frameTranslationSize; ++i) - m[frameTranslation[i][0]] = frameTranslation[i][1]; + if(m.isEmpty()) + for(size_t i = 0; i < frameTranslationSize; ++i) + m[frameTranslation[i][0]] = frameTranslation[i][1]; return m; } From f859fcf82ac9f29b7aeb399f13a9cab2610c64f6 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 26 Feb 2012 18:07:02 +0100 Subject: [PATCH 37/39] Add support for Unknown TXXX frames. --- taglib/mpeg/id3v2/id3v2frame.cpp | 21 +++++++++------ taglib/mpeg/id3v2/id3v2tag.cpp | 44 +++++++++++++++++++------------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 7a7c0da1..ed023123 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -431,9 +431,16 @@ 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') + if(id == "TXXX") { + const UserTextIdentificationFrame *txxxFrame = dynamic_cast< const UserTextIdentificationFrame* >(this); + if(txxxFrame != NULL) + return txxxFrame->asProperties(); + else { + PropertyMap m; + m.unsupportedData().append("UNKNOWN"); + return m; + } + } else if(id[0] == 'T') return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties(); else if(id == "WXXX") return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties(); @@ -443,11 +450,9 @@ PropertyMap Frame::asProperties() const 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; - } + PropertyMap m; + m.unsupportedData().append(id); + return m; } void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties, diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 4731ba55..49c0c517 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -350,24 +350,32 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) // by the PropertyMap interface. Three special cases exist: TXXX, WXXX, and COMM // frames may also be unsupported if their description() is not a valid key. for(StringList::ConstIterator it = properties.begin(); it != properties.end(); ++it){ - ByteVector id = it->substr(0,4).data(String::Latin1); - if(id == "TXXX") { - String description = it->substr(5); - Frame *frame = UserTextIdentificationFrame::find(this, description); - if(frame) - removeFrame(frame); - } else if(id == "WXXX") { - String description = it->substr(5); - Frame *frame = UserUrlLinkFrame::find(this, description); - if(frame) - removeFrame(frame); - } else if(id == "COMM") { - String description = it->substr(5); - Frame *frame = CommentsFrame::findByDescription(this, description); - if(frame) - removeFrame(frame); - } else - removeFrames(id); // there should be only one frame with "id" + if(*it == "UNKNOWN") { + // delete all unknown frames + FrameList l = frameList(); + for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) + if (dynamic_cast(*fit) != NULL) + removeFrame(*fit); + } else { + ByteVector id = it->substr(0,4).data(String::Latin1); + if(id == "TXXX") { + String description = it->substr(5); + Frame *frame = UserTextIdentificationFrame::find(this, description); + if(frame) + removeFrame(frame); + } else if(id == "WXXX") { + String description = it->substr(5); + Frame *frame = UserUrlLinkFrame::find(this, description); + if(frame) + removeFrame(frame); + } else if(id == "COMM") { + String description = it->substr(5); + Frame *frame = CommentsFrame::findByDescription(this, description); + if(frame) + removeFrame(frame); + } else + removeFrames(id); // there should be only one frame with "id" + } } } From 6e6d823992fe53ede880889eb6e78709c84570e3 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 26 Feb 2012 18:38:03 +0100 Subject: [PATCH 38/39] Removed quodlibet special case handling --- taglib/mpeg/id3v2/frames/textidentificationframe.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index d68d0dfe..a026bca9 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -380,9 +380,7 @@ void UserTextIdentificationFrame::setDescription(const String &s) PropertyMap UserTextIdentificationFrame::asProperties() const { String tagName = description(); - // 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 From f5a25182734ba0f15b698f8dc4cc29509df89293 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sun, 26 Feb 2012 19:21:57 +0100 Subject: [PATCH 39/39] Fixed handling of UnknownFrames in ID3v2. - If an unknown frame with id "XXXX" occurs, an entry "UNKNOWN/XXXX" is added to unsupportedData(). The removeUnsupportedProperties() method in turn removes all unknown frames with id "XXXX" if it encounters a string "UNKNOWN/XXXX" in the given list. - Implemented findByDescription() to UnsynchronizedLyricsFrame in order to support removal of lyrics frames with unsupported keys. - Adapted id3v2 test case to new QuodLibet policy. --- .../frames/unsynchronizedlyricsframe.cpp | 12 +++++ .../id3v2/frames/unsynchronizedlyricsframe.h | 9 ++++ taglib/mpeg/id3v2/id3v2frame.cpp | 19 ++++---- taglib/mpeg/id3v2/id3v2tag.cpp | 47 ++++++++++--------- taglib/mpeg/id3v2/id3v2tag.h | 11 +++-- tests/test_id3v2.cpp | 9 +++- 6 files changed, 69 insertions(+), 38 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp index 6719a9b3..9d76164d 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.cpp @@ -27,6 +27,7 @@ #include "unsynchronizedlyricsframe.h" #include +#include #include #include @@ -125,6 +126,17 @@ PropertyMap UnsynchronizedLyricsFrame::asProperties() const return map; } +UnsynchronizedLyricsFrame *UnsynchronizedLyricsFrame::findByDescription(const ID3v2::Tag *tag, const String &d) // static +{ + ID3v2::FrameList lyrics = tag->frameList("USLT"); + + for(ID3v2::FrameList::ConstIterator it = lyrics.begin(); it != lyrics.end(); ++it){ + UnsynchronizedLyricsFrame *frame = dynamic_cast(*it); + if(frame && frame->description() == d) + return frame; + } + return 0; +} //////////////////////////////////////////////////////////////////////////////// // protected members //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h index 2a3c5f9a..3af354fc 100644 --- a/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h +++ b/taglib/mpeg/id3v2/frames/unsynchronizedlyricsframe.h @@ -147,6 +147,15 @@ namespace TagLib { */ PropertyMap asProperties() const; + /*! + * LyricsFrames each have a unique description. This searches for a lyrics + * frame with the decription \a d and returns a pointer to it. If no + * frame is found that matches the given description null is returned. + * + * \see description() + */ + static UnsynchronizedLyricsFrame *findByDescription(const Tag *tag, const String &d); + protected: // Reimplementations. diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index ed023123..6b45f223 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -43,6 +43,7 @@ #include "frames/urllinkframe.h" #include "frames/unsynchronizedlyricsframe.h" #include "frames/commentsframe.h" +#include "frames/unknownframe.h" using namespace TagLib; using namespace ID3v2; @@ -429,18 +430,16 @@ ByteVector Frame::keyToFrameID(const String &s) PropertyMap Frame::asProperties() const { + if(dynamic_cast< const UnknownFrame *>(this)) { + PropertyMap m; + m.unsupportedData().append("UNKNOWN/" + frameID()); + return m; + } const ByteVector &id = frameID(); // workaround until this function is virtual - if(id == "TXXX") { - const UserTextIdentificationFrame *txxxFrame = dynamic_cast< const UserTextIdentificationFrame* >(this); - if(txxxFrame != NULL) - return txxxFrame->asProperties(); - else { - PropertyMap m; - m.unsupportedData().append("UNKNOWN"); - return m; - } - } else if(id[0] == 'T') + 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(); diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 49c0c517..ce228330 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -346,35 +346,36 @@ PropertyMap ID3v2::Tag::properties() const void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) { - // entries of unsupportedData() are usually frame IDs which are not supported - // by the PropertyMap interface. Three special cases exist: TXXX, WXXX, and COMM - // frames may also be unsupported if their description() is not a valid key. for(StringList::ConstIterator it = properties.begin(); it != properties.end(); ++it){ - if(*it == "UNKNOWN") { - // delete all unknown frames - FrameList l = frameList(); + if(it->startsWith("UNKNOWN/")) { + String frameID = it->substr(String("UNKNOWN/").size()); + if(frameID.size() != 4) + continue; // invalid specification + ByteVector id = frameID.data(String::Latin1); + // delete all unknown frames of given type + FrameList l = frameList(id); for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) if (dynamic_cast(*fit) != NULL) removeFrame(*fit); + } else if(it->size() == 4){ + ByteVector id = it->data(String::Latin1); + removeFrames(id); } else { ByteVector id = it->substr(0,4).data(String::Latin1); - if(id == "TXXX") { - String description = it->substr(5); - Frame *frame = UserTextIdentificationFrame::find(this, description); - if(frame) - removeFrame(frame); - } else if(id == "WXXX") { - String description = it->substr(5); - Frame *frame = UserUrlLinkFrame::find(this, description); - if(frame) - removeFrame(frame); - } else if(id == "COMM") { - String description = it->substr(5); - Frame *frame = CommentsFrame::findByDescription(this, description); - if(frame) - removeFrame(frame); - } else - removeFrames(id); // there should be only one frame with "id" + if(it->size() <= 5) + continue; // invalid specification + String description = it->substr(5); + Frame *frame; + if(id == "TXXX") + frame = UserTextIdentificationFrame::find(this, description); + else if(id == "WXXX") + frame = UserUrlLinkFrame::find(this, description); + else if(id == "COMM") + frame = CommentsFrame::findByDescription(this, description); + else if(id == "USLT") + frame = UnsynchronizedLyricsFrame::findByDescription(this, description); + if(frame) + removeFrame(frame); } } } diff --git a/taglib/mpeg/id3v2/id3v2tag.h b/taglib/mpeg/id3v2/id3v2tag.h index 3728868a..94784e76 100644 --- a/taglib/mpeg/id3v2/id3v2tag.h +++ b/taglib/mpeg/id3v2/id3v2tag.h @@ -293,9 +293,14 @@ namespace TagLib { /*! * 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. + * \a properties must be taken from properties().unsupportedData(); they + * are of one of the following forms: + * - a four-character frame ID, if the ID3 specification allows only one + * frame with that ID (thus, the frame is uniquely determined) + * - frameID + "/" + description(), when the ID is one of "TXXX", "WXXX", + * "COMM", or "USLT", + * - "UNKNOWN/" + frameID, for frames that could not be parsed by TagLib. + * In that case, *all* unknown frames with the given ID will be removed. */ void removeUnsupportedProperties(const StringList &properties); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 530b3f3d..49ffef0d 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -558,10 +558,15 @@ public: MPEG::File f(newname.c_str()); PropertyMap dict = f.ID3v2Tag(false)->properties(); CPPUNIT_ASSERT_EQUAL(uint(6), dict.size()); + + CPPUNIT_ASSERT(dict.contains("USERTEXTDESCRIPTION1")); + CPPUNIT_ASSERT(dict.contains("QuodLibet::USERTEXTDESCRIPTION2")); + CPPUNIT_ASSERT_EQUAL(uint(2), dict["USERTEXTDESCRIPTION1"].size()); + CPPUNIT_ASSERT_EQUAL(uint(2), dict["QuodLibet::USERTEXTDESCRIPTION2"].size()); CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]); CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]); - CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION2"][0]); - CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION2"][1]); + CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["QuodLibet::USERTEXTDESCRIPTION2"][0]); + CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["QuodLibet::USERTEXTDESCRIPTION2"][1]); CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"].front());