PropertyMap UserTextIdentificationFrame::asProperties() const
{
- String tagName = description();
-
PropertyMap map;
- String key = tagName.upper();
- if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list
- map.unsupportedData().append(L"TXXX/" + description());
- else {
- StringList v = fieldList();
- for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it)
- if(*it != description())
- map.insert(key, *it);
- }
+ String tagName = txxxToKey(description());
+ StringList v = fieldList();
+ for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it)
+ if(it != v.begin())
+ map.insert(tagName, *it);
return map;
}
***************************************************************************/
#include <tbytevectorlist.h>
+#include <tpropertymap.h>
#include <tdebug.h>
+#include "id3v2tag.h"
#include "uniquefileidentifierframe.h"
using namespace TagLib;
return String::null;
}
+PropertyMap UniqueFileIdentifierFrame::asProperties() const
+{
+ PropertyMap map;
+ if(d->owner == "http://musicbrainz.org") {
+ map.insert("MUSICBRAINZ_RECORDINGID", String(d->identifier));
+ }
+ else {
+ map.unsupportedData().append(frameID() + String("/") + d->owner);
+ }
+ return map;
+}
+
+UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static
+{
+ ID3v2::FrameList comments = tag->frameList("UFID");
+
+ for(ID3v2::FrameList::ConstIterator it = comments.begin();
+ it != comments.end();
+ ++it)
+ {
+ UniqueFileIdentifierFrame *frame = dynamic_cast<UniqueFileIdentifierFrame *>(*it);
+ if(frame && frame->owner() == o)
+ return frame;
+ }
+
+ return 0;
+}
+
void UniqueFileIdentifierFrame::parseFields(const ByteVector &data)
{
if(data.size() < 1) {
virtual String toString() const;
+ PropertyMap asProperties() const;
+
+ /*!
+ * UFID frames each have a unique owner. This searches for a UFID
+ * frame with the owner \a o and returns a pointer to it.
+ *
+ * \see owner()
+ */
+ static UniqueFileIdentifierFrame *findByOwner(const Tag *tag, const String &o);
+
protected:
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
#include "frames/urllinkframe.h"
#include "frames/unsynchronizedlyricsframe.h"
#include "frames/commentsframe.h"
+#include "frames/uniquefileidentifierframe.h"
#include "frames/unknownframe.h"
using namespace TagLib;
return frame;
}
}
+ if(key == "MUSICBRAINZ_RECORDINGID" && values.size() == 1) {
+ UniqueFileIdentifierFrame *frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8));
+ 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" || key.startsWith(lyricsPrefix)) && values.size() == 1){
return frame;
}
// if non of the above cases apply, we use a TXXX frame with the key as description
- return new UserTextIdentificationFrame(key, values, String::UTF8);
+ return new UserTextIdentificationFrame(keyToTXXX(key), values, String::UTF8);
}
Frame::~Frame()
//{ "USLT", "LYRICS" }, handled specially
};
+static const TagLib::uint txxxFrameTranslationSize = 7;
+static const char *txxxFrameTranslation[][2] = {
+ { "MusicBrainz Album Id", "MUSICBRAINZ_RELEASEID" },
+ { "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
+ { "MusicBrainz Album Artist Id", "MUSICBRAINZ_RELEASEARTISTID" },
+ { "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
+ { "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
+ { "Acoustid Id", "ACOUSTID_ID" },
+ { "Acoustid Fingerprint", "ACOUSTID_FINGERPRINT" },
+ { "MusicIP PUID", "MUSICIP_PUID" },
+};
+
Map<ByteVector, String> &idMap()
{
static Map<ByteVector, String> m;
return m;
}
+Map<String, String> &txxxMap()
+{
+ static Map<String, String> m;
+ if(m.isEmpty()) {
+ for(size_t i = 0; i < txxxFrameTranslationSize; ++i) {
+ String key = String(txxxFrameTranslation[i][0]).upper();
+ m[key] = txxxFrameTranslation[i][1];
+ }
+ }
+ return m;
+}
+
// list of deprecated frames and their successors
static const TagLib::uint deprecatedFramesSize = 4;
static const char *deprecatedFrames[][2] = {
return ByteVector::null;
}
+String Frame::txxxToKey(const String &description)
+{
+ Map<String, String> &m = txxxMap();
+ String d = description.upper();
+ if(m.contains(d))
+ return m[d];
+ return d;
+}
+
+String Frame::keyToTXXX(const String &s)
+{
+ static Map<String, String> m;
+ if(m.isEmpty())
+ for(size_t i = 0; i < txxxFrameTranslationSize; ++i)
+ m[txxxFrameTranslation[i][1]] = txxxFrameTranslation[i][0];
+ if(m.contains(s.upper()))
+ return m[s];
+ return s;
+}
+
PropertyMap Frame::asProperties() const
{
if(dynamic_cast< const UnknownFrame *>(this)) {
return dynamic_cast< const CommentsFrame* >(this)->asProperties();
else if(id == "USLT")
return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties();
+ else if(id == "UFID")
+ return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties();
PropertyMap m;
m.unsupportedData().append(id);
return m;
*/
static String frameIDToKey(const ByteVector &);
+ /*!
+ * Returns an appropriate TXXX frame description for the given free-form tag key.
+ */
+ static String keyToTXXX(const String &);
+
+ /*!
+ * Returns a free-form tag name for the given ID3 frame description.
+ */
+ static String txxxToKey(const String &);
/*!
* This helper function splits the PropertyMap \a original into three ProperytMaps
for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++)
if (dynamic_cast<const UnknownFrame *>(*fit) != 0)
removeFrame(*fit);
- } else if(it->size() == 4){
+ }
+ else if(it->size() == 4){
ByteVector id = it->data(String::Latin1);
removeFrames(id);
- } else {
+ }
+ else {
ByteVector id = it->substr(0,4).data(String::Latin1);
if(it->size() <= 5)
continue; // invalid specification
frame = CommentsFrame::findByDescription(this, description);
else if(id == "USLT")
frame = UnsynchronizedLyricsFrame::findByDescription(this, description);
+ else if(id == "UFID")
+ frame = UniqueFileIdentifierFrame::findByOwner(this, description);
if(frame)
removeFrame(frame);
}
* Note that most metadata formats pose additional conditions on the tag keys. The
* most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of
* length between 2 and 16.
+ *
+ * This class can contain any tags, but here is a list of "well-known" tags that
+ * you might want to use:
+ *
+ * Basic tags:
+ *
+ * - TITLE
+ * - ALBUM
+ * - ARTIST
+ * - ALBUMARTIST
+ * - SUBTITLE
+ * - TRACKNUMBER
+ * - DISCNUMBER
+ * - DATE
+ * - ORIGINALDATE
+ * - GENRE
+ * - COMMENT
+ *
+ * Credits:
+ *
+ * - COMPOSER
+ * - LYRICIST
+ * - CONDUCTOR
+ * - REMIXER
+ * - PERFORMER:<XXXX>
+ *
+ * Other tags:
+ *
+ * - ISRC
+ * - ASIN
+ * - BPM
+ * - COPYRIGHT
+ * - ENCODEDBY
+ * - MOOD
+ * - COMMENT
+ *
+ * MusicBrainz identifiers:
+ *
+ * - MUSICBRAINZ_RECORDINGID
+ * - MUSICBRAINZ_RELEASEID
+ * - MUSICBRAINZ_RELEASEGROUPID
+ * - MUSICBRAINZ_WORKID
+ * - MUSICBRAINZ_ARTISTID
+ * - MUSICBRAINZ_RELEASEARTISTID
+ * - ACOUSTID_ID
+ * - ACOUSTID_FINGERPRINT
+ * - MUSICIP_PUID
+ *
*/
class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap
CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front());
CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size());
- CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front());
+ CPPUNIT_ASSERT_EQUAL(String("UFID/supermihi@web.de"), dict.unsupportedData().front());
}
void testPropertyInterface2()
frame5->setText(tmclData);
tag.addFrame(frame5);
+ ID3v2::UniqueFileIdentifierFrame *frame6 = new ID3v2::UniqueFileIdentifierFrame("http://musicbrainz.org", "152454b9-19ba-49f3-9fc9-8fc26545cf41");
+ tag.addFrame(frame6);
+
+ ID3v2::UniqueFileIdentifierFrame *frame7 = new ID3v2::UniqueFileIdentifierFrame("http://example.com", "123");
+ tag.addFrame(frame7);
+
+ ID3v2::UserTextIdentificationFrame *frame8 = new ID3v2::UserTextIdentificationFrame();
+ frame8->setDescription("MusicBrainz Album Id");
+ frame8->setText("95c454a5-d7e0-4d8f-9900-db04aca98ab3");
+ tag.addFrame(frame8);
+
PropertyMap properties = tag.properties();
- CPPUNIT_ASSERT_EQUAL(2u, properties.unsupportedData().size());
+ CPPUNIT_ASSERT_EQUAL(3u, properties.unsupportedData().size());
CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL"));
CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC"));
+ CPPUNIT_ASSERT(properties.unsupportedData().contains("UFID/http://example.com"));
CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN"));
CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO"));
CPPUNIT_ASSERT(properties.contains("LYRICS"));
CPPUNIT_ASSERT(properties.contains("LYRICS:TEST"));
+ CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RECORDINGID"));
+ CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_RECORDINGID"].front());
+
+ CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RELEASEID"));
+ CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_RELEASEID"].front());
+
tag.removeUnsupportedProperties(properties.unsupportedData());
CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty());
CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty());
+ CPPUNIT_ASSERT_EQUAL((ID3v2::UniqueFileIdentifierFrame *)0, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://example.com"));
+ CPPUNIT_ASSERT_EQUAL(frame6, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://musicbrainz.org"));
}
void testDeleteFrame()