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
)
set(id3v2_SRCS
+ mpeg/id3v2/id3v2dicttools.cpp
mpeg/id3v2/id3v2framefactory.cpp
mpeg/id3v2/id3v2synchdata.cpp
mpeg/id3v2/id3v2tag.cpp
--- /dev/null
+/***************************************************************************
+ 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<ByteVector, String> 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<ByteVector> 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);
+ }
+ }
+}
--- /dev/null
+/***************************************************************************
+ 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<ByteVector, ByteVector> 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_ */
#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;
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::<tagName> instead of simply
+ // <tagName>.
+ 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
*/
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.
*/
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;
*/
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".
#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<String, StringList> TagDict;
+
//! A simple, generic interface to common audio meta data fields
/*!
CPPUNIT_TEST(testDowngradeTo23);
// CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together
CPPUNIT_TEST(testCompressedFrameWithBrokenLength);
+ CPPUNIT_TEST(testDictInterface);
CPPUNIT_TEST_SUITE_END();
public:
CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->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);
CPPUNIT_TEST_SUITE(TestOGG);
CPPUNIT_TEST(testSimple);
CPPUNIT_TEST(testSplitPackets);
+ CPPUNIT_TEST(testDictInterface1);
+ CPPUNIT_TEST(testDictInterface2);
CPPUNIT_TEST_SUITE_END();
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);