From 58db919e435f4656a4ef67c5fd2582668b6f6863 Mon Sep 17 00:00:00 2001 From: Michael Helmling Date: Sat, 27 Aug 2011 01:18:21 +0200 Subject: [PATCH] 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 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 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]); }