Implemented asProperties() in all relevant textual frames.

This commit is contained in:
Michael Helmling
2012-01-22 17:08:02 +01:00
parent a5e45f196b
commit 0c8e5bbec8
16 changed files with 212 additions and 50 deletions

View File

@ -55,7 +55,6 @@ 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
@ -139,7 +138,6 @@ set(id3v1_SRCS
)
set(id3v2_SRCS
mpeg/id3v2/id3v2dicttools.cpp
mpeg/id3v2/id3v2framefactory.cpp
mpeg/id3v2/id3v2synchdata.cpp
mpeg/id3v2/id3v2tag.cpp

View File

@ -109,6 +109,19 @@ void CommentsFrame::setTextEncoding(String::Type encoding)
d->textEncoding = encoding;
}
PropertyMap CommentsFrame::asDescription() const
{
String key = PropertyMap::prepareKey(description());
PropertyMap map;
if(key.isEmpty())
key = "COMMENT";
if(key.isNull())
map.unsupportedData().append(L"COMM/" + description());
else
map.insert(key, text());
return map;
}
CommentsFrame *CommentsFrame::findByDescription(const ID3v2::Tag *tag, const String &d) // static
{
ID3v2::FrameList comments = tag->frameList("COMM");

View File

@ -136,6 +136,17 @@ namespace TagLib {
*/
void setTextEncoding(String::Type encoding);
/*!
* Parses this frame as PropertyMap.
* - description() will be used as key
* - if description() is empty, the key will be "COMMENT"
* - if description() is not a valid PropertyMap key, the frame will be
* marked unsupported by an entry "COMM/<description>" in the unsupportedData()
* attribute of the returned map.
* - The single value will be the frame's text().
*/
PropertyMap asDescription() const;
/*!
* Comments each have a unique description. This searches for a comment
* frame with the decription \a d and returns a pointer to it. If no

View File

@ -92,6 +92,40 @@ void TextIdentificationFrame::setTextEncoding(String::Type encoding)
d->textEncoding = encoding;
}
PropertyMap TextIdentificationFrame::asProperties() const
{
if(frameID() == "TIPL")
return makeTIPLProperties();
if(frameID() == "TMCL")
return makeTMCLProperties();
PropertyMap map;
String tagName = frameIDToTagName(frameID());
if(tagName.isNull()) {
map.unsupportedData().append(frameID());
return map;
}
StringList values = 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 it = values.begin(); it != values.end(); ++it) {
bool ok = false;
int test = it->toInt(&ok); // test if the genre value is an integer
if(ok)
*it = ID3v1::genre(test);
}
} else if(tagName == "DATE") {
for (StringList::Iterator it = values.begin(); it != values.end(); ++it) {
// 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 = it->find("T");
if (tpos != -1)
(*it)[tpos] = ' ';
}
}
return KeyValuePair(tagName, values);
}
////////////////////////////////////////////////////////////////////////////////
// TextIdentificationFrame protected members
////////////////////////////////////////////////////////////////////////////////
@ -170,6 +204,63 @@ TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data, Header
parseFields(fieldData(data));
}
// array of allowed TIPL prefixes and their corresponding key value
static const uint involvedPeopleSize = 5;
static const char* involvedPeople[2] = {
{"ARRANGER", "ARRANGER"},
{"ENGINEER", "ENGINEER"},
{"PRODUCER", "PRODUCER"},
{"DJ-MIX", "DJMIXER"},
{"MIX", "MIXER"}
};
PropertyMap TextIdentificationFrame::makeTIPLProperties() const
{
PropertyMap map;
if(fieldList().size() % 2 != 0){
// according to the ID3 spec, TIPL must contain an even number of entries
map.unsupportedData().append(frameID());
return map;
}
for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) {
bool found = false;
for(uint i = 0; i < involvedPeopleSize; ++i)
if(*it == involvedPeople[i][0]) {
map.insert(involvedPeople[i][1], (++it).split(","));
found = true;
break;
}
if(!found){
// invalid involved role -> mark whole frame as unsupported in order to be consisten with writing
map.clear();
map.unsupportedData().append(frameID());
return map;
}
}
return map;
}
PropertyMap TextIdentificationFrame::makeTMCLProperties() const
{
PropertyMap map;
if(fieldList().size() % 2 != 0){
// according to the ID3 spec, TMCL must contain an even number of entries
map.unsupportedData().append(frameID());
return map;
}
for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) {
String key = PropertyMap::prepareKey(*it);
if(key.isNull()) {
// instrument is not a valid key -> frame unsupported
map.clear();
map.unsupportedData().append(frameID());
return map;
}
map.insert(key, (++it).split(","));
}
return map;
}
////////////////////////////////////////////////////////////////////////////////
// UserTextIdentificationFrame public members
////////////////////////////////////////////////////////////////////////////////
@ -241,22 +332,17 @@ void UserTextIdentificationFrame::setDescription(const String &s)
PropertyMap UserTextIdentificationFrame::asProperties() const
{
String tagName = description();
StringList l(fieldList());
// this is done because taglib stores the description also as first entry
// in the field list. (why?)
StringList::Iterator tagIt = l.find(tagName);
if(tagIt != l.end())
l.erase(tagIt);
// Quodlibet/Exfalso use QuodLibet::<tagname> if you set an arbitrary ID3
// tag.
// Quodlibet/Exfalso use QuodLibet::<tagname> if you set an arbitrary ID3 tag.
int pos = tagName.find("::");
tagName = (pos != -1) ? tagName.substr(pos+2).upper() : tagName.upper();
PropertyMap map;
String key = map.prepareKey(tagName);
if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list
map.unsupportedData().append("TXXX/" + description());
map.unsupportedData().append(L"TXXX/" + description());
else
map.insert(key, l);
for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it)
if(*it != description())
map.insert(key, *it);
return map;
}

View File

@ -173,6 +173,8 @@ namespace TagLib {
*/
StringList fieldList() const;
PropertyMap asProperties() const;
protected:
// Reimplementations.
@ -188,6 +190,16 @@ namespace TagLib {
TextIdentificationFrame(const TextIdentificationFrame &);
TextIdentificationFrame &operator=(const TextIdentificationFrame &);
/*!
* Parses the special structure of a TIPL frame
* Only the whitelisted roles "ARRANGER", "ENGINEER", "PRODUCER",
* "DJMIXER" (ID3: "DJ-MIX") and "MIXER" (ID3: "MIX") are allowed.
*/
PropertyMap makeTIPLProperties() const;
/*!
* Parses the special structure of a TMCL frame.
*/
PropertyMap makeTMCLProperties() const;
class TextIdentificationFramePrivate;
TextIdentificationFramePrivate *d;
};
@ -237,7 +249,17 @@ namespace TagLib {
void setText(const StringList &fields);
/*!
* Reimplement function.
* A UserTextIdentificationFrame is parsed into a PropertyMap as follows:
* - the key is the frame's description, uppercased
* - if the description contains '::', only the substring after that
* separator is considered as key (compatibility with exfalso)
* - if the above rules don't yield a valid key (e.g. containing non-ASCII
* characters), the returned map will contain an entry "TXXX/<description>"
* in its unsupportedData() list.
* - The values will be copies of the fieldList().
* - If the description() appears as value in fieldList(), it will be omitted
* in the value list, in order to be compatible with TagLib which copies
* the description() into the fieldList().
*/
PropertyMap asProperties() const;

View File

@ -111,6 +111,13 @@ void UnsynchronizedLyricsFrame::setTextEncoding(String::Type encoding)
d->textEncoding = encoding;
}
PropertyMap UnsynchronizedLyricsFrame::asProperties() const
{
PropertyMap map;
map.insert("LYRICS", text());
return map;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

View File

@ -134,6 +134,13 @@ namespace TagLib {
*/
void setTextEncoding(String::Type encoding);
/*!
* Parses this frame as PropertyMap. The returned map will contain a single key
* "LYRICS" with the text() as single value.
*/
PropertyMap asProperties() const;
protected:
// Reimplementations.

View File

@ -78,6 +78,18 @@ String UrlLinkFrame::toString() const
return url();
}
PropertyMap UrlLinkFrame::asProperties() const
{
String key = frameIDToKey(frameID());
PropertyMap map;
if(key.isNull())
// unknown W*** frame - this normally shouldn't happen
map.unsupportedData().append(frameID());
else
map.insert(key, url());
return map;
}
void UrlLinkFrame::parseFields(const ByteVector &data)
{
d->url = String(data);
@ -139,6 +151,19 @@ void UserUrlLinkFrame::setDescription(const String &s)
d->description = s;
}
PropertyMap UserUrlLinkFrame::asProperties() const
{
String key = PropertyMap::prepareKey(description());
PropertyMap map;
if(key.isEmpty())
key = "URL";
if(key.isNull())
map.unsupportedData().append(L"WXXX/" + description());
else
map.insert(key, url());
return map;
}
void UserUrlLinkFrame::parseFields(const ByteVector &data)
{
if(data.size() < 2) {

View File

@ -68,6 +68,7 @@ namespace TagLib {
virtual void setText(const String &s);
virtual String toString() const;
PropertyMap asProperties() const;
protected:
virtual void parseFields(const ByteVector &data);
@ -150,6 +151,16 @@ namespace TagLib {
*/
void setDescription(const String &s);
/*!
* Parses the UserUrlLinkFrame as PropertyMap. The description() is taken as key,
* and the URL as single value.
* - if description() is empty, the key will be "URL".
* - otherwise, if description() is not a valid key (e.g. containing non-ASCII
* characters), the returned map will contain an entry "WXXX/<description>"
* in its unsupportedData() list.
*/
PropertyMap asProperties() const;
protected:
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;

View File

@ -38,6 +38,7 @@
#include "id3v2frame.h"
#include "id3v2synchdata.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace ID3v2;
@ -262,7 +263,7 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc
return checkEncoding(fields, encoding, header()->version());
}
static const uint frameTranslationSize = 55;
static const uint frameTranslationSize = 53;
static const char *frameTranslation[][2] = {
// Text information frames
{ "TALB", "ALBUM"},
@ -283,14 +284,14 @@ static const char *frameTranslation[][2] = {
{ "TENC", "ENCODEDBY" },
{ "TEXT", "LYRICIST" },
{ "TFLT", "FILETYPE" },
{ "TIPL", "INVOLVEDPEOPLE" },
//{ "TIPL", "INVOLVEDPEOPLE" }, handled separately
{ "TIT1", "CONTENTGROUP" },
{ "TIT2", "TITLE"},
{ "TIT3", "SUBTITLE" },
{ "TKEY", "INITIALKEY" },
{ "TLAN", "LANGUAGE" },
{ "TLEN", "LENGTH" },
{ "TMCL", "MUSICIANCREDITS" },
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
{ "TMED", "MEDIATYPE" },
{ "TMOO", "MOOD" },
{ "TOAL", "ORIGINALALBUM" },

View File

@ -33,6 +33,7 @@
namespace TagLib {
class StringList;
class PropertyMap;
namespace ID3v2 {

View File

@ -33,6 +33,14 @@
#include "id3v2synchdata.h"
#include "tbytevector.h"
#include "id3v1genres.h"
#include "tpropertymap.h"
#include "frames/textidentificationframe.h"
#include "frames/commentsframe.h"
#include "frames/urllinkframe.h"
#include "frames/uniquefileidentifierframe.h"
#include "frames/unsynchronizedlyricsframe.h"
#include "frames/unknownframe.h"
using namespace TagLib;
using namespace ID3v2;
@ -329,23 +337,12 @@ void ID3v2::Tag::removeFrames(const ByteVector &id)
PropertyMap ID3v2::Tag::properties() const
{
PropertyMap properties;
for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) {
ByteVector id = (*it)->frameID();
if (ignored(id)) {
debug("toDict() found ignored id3 frame: " + id);
} else if (deprecated(id)) {
debug("toDict() found deprecated id3 frame: " + id);
} else {
// in the future, something like dict[frame->tagName()].append(frame->values())
// might replace the following lines.
KeyValuePair kvp = parseFrame(*it);
dict[kvp.first].append(kvp.second);
}
}
return dict;
for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it)
properties.merge((*it)->asProperties());
return properties;
}
void ID3v2::Tag::fromDict(const TagDict &dict)
PropertyMap ID3v2::Tag::setProperties(const PropertyMap &properties)
{
FrameList toRemove;
// first find out what frames to remove; we do not remove in-place

View File

@ -35,6 +35,7 @@
#include "mpegfile.h"
#include "mpegheader.h"
#include "tpropertymap.h"
using namespace TagLib;

View File

@ -171,21 +171,6 @@ 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
{

View File

@ -73,9 +73,6 @@ namespace TagLib {
virtual void setTrack(uint i);
virtual bool isEmpty() const;
virtual TagDict toDict() const;
virtual void fromDict(const TagDict &);
template <class T> T *access(int index, bool create)
{
if(!create || tag(index))

View File

@ -35,7 +35,7 @@ namespace TagLib {
* This map implements a generic representation of textual audio metadata
* ("tags") realized as pairs of a case-insensitive key
* and a nonempty list of corresponding values, each value being an an arbitrary
* Unicode String.
* unicode String.
* The key has the same restrictions as in the vorbis comment specification,
* i.e. it must contain at least one character; all printable ASCII characters
* except '=' and '~' are allowed.