Add support for reading MusicBrainz IDs from ID3v2 tags to PropertyMap

This commit is contained in:
Lukáš Lalinský 2012-11-21 17:21:30 +01:00
parent 15b601f053
commit e75d6f616c
8 changed files with 182 additions and 16 deletions

View File

@ -381,18 +381,12 @@ void UserTextIdentificationFrame::setDescription(const String &s)
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;
}

View File

@ -24,8 +24,10 @@
***************************************************************************/
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <tdebug.h>
#include "id3v2tag.h"
#include "uniquefileidentifierframe.h"
using namespace TagLib;
@ -87,6 +89,34 @@ String UniqueFileIdentifierFrame::toString() const
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) {

View File

@ -94,6 +94,16 @@ namespace TagLib {
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;

View File

@ -44,6 +44,7 @@
#include "frames/urllinkframe.h"
#include "frames/unsynchronizedlyricsframe.h"
#include "frames/commentsframe.h"
#include "frames/uniquefileidentifierframe.h"
#include "frames/unknownframe.h"
using namespace TagLib;
@ -126,6 +127,10 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
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){
@ -151,7 +156,7 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) //
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()
@ -387,6 +392,18 @@ static const char *frameTranslation[][2] = {
//{ "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;
@ -396,6 +413,18 @@ Map<ByteVector, String> &idMap()
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] = {
@ -435,6 +464,26 @@ ByteVector Frame::keyToFrameID(const String &s)
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)) {
@ -456,6 +505,8 @@ PropertyMap Frame::asProperties() const
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;

View File

@ -274,6 +274,15 @@ namespace TagLib {
*/
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

View File

@ -379,10 +379,12 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties)
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
@ -396,6 +398,8 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties)
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);
}

View File

@ -40,6 +40,54 @@ namespace TagLib {
* 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

View File

@ -624,7 +624,7 @@ public:
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()
@ -657,11 +657,23 @@ public:
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"));
@ -671,9 +683,17 @@ public:
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()