From b262180857f080af6eb8b3f648f210fd149db2af Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Fri, 26 Aug 2011 21:48:40 +0200 Subject: [PATCH] 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);