mirror of
https://github.com/taglib/taglib.git
synced 2025-07-18 13:04:18 -04:00
Implemented asProperties() in all relevant textual frames.
This commit is contained in:
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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" },
|
||||
|
@ -33,6 +33,7 @@
|
||||
namespace TagLib {
|
||||
|
||||
class StringList;
|
||||
class PropertyMap;
|
||||
|
||||
namespace ID3v2 {
|
||||
|
||||
|
@ -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
|
||||
|
@ -35,6 +35,7 @@
|
||||
|
||||
#include "mpegfile.h"
|
||||
#include "mpegheader.h"
|
||||
#include "tpropertymap.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
Reference in New Issue
Block a user