Started to work on ID3v2.

This commit is contained in:
Michael Helmling 2012-01-21 23:05:59 +01:00
parent e4d955d6ef
commit a5e45f196b
12 changed files with 308 additions and 94 deletions

View File

@ -113,27 +113,34 @@ TagLib::Tag *MPC::File::tag() const
return &d->tag;
}
TagLib::TagDict MPC::File::toDict(void) const
PropertyMap MPC::File::properties() const
{
// once Tag::toDict() is virtual, this case distinction could actually be done
// within TagUnion.
if (d->hasAPE)
return d->tag.access<APE::Tag>(APEIndex, false)->toDict();
if (d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->toDict();
return TagLib::TagDict();
if(d->hasAPE)
return d->tag.access<APE::Tag>(APEIndex, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->properties();
return PropertyMap();
}
void MPC::File::fromDict(const TagDict &dict)
void MPC::File::removeUnsupportedProperties(const StringList &properties)
{
if (d->hasAPE)
d->tag.access<APE::Tag>(APEIndex, false)->fromDict(dict);
else if (d->hasID3v1)
d->tag.access<ID3v1::Tag>(ID3v1Index, false)->fromDict(dict);
else
d->tag.access<APE::Tag>(APEIndex, true)->fromDict(dict);
if(d->hasAPE)
d->tag.access<APE::Tag>(APEIndex, false)->removeUnsupportedProperties(properties);
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(ID3v1Index, false)->removeUnsupportedProperties(properties);
}
PropertyMap MPC::File::setProperties(const PropertyMap &properties)
{
if(d->hasAPE)
return d->tag.access<APE::Tag>(APEIndex, false)->setProperties(properties);
else if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->setProperties(properties);
else
return d->tag.access<APE::Tag>(APE, true)->setProperties(properties);
}
MPC::Properties *MPC::File::audioProperties() const
{
return d->properties;

View File

@ -109,18 +109,20 @@ namespace TagLib {
virtual TagLib::Tag *tag() const;
/*!
* Implements the unified tag dictionary interface -- export function.
* Implements the unified property interface -- export function.
* If the file contains both an APE and an ID3v1 tag, only the APE
* tag will be converted to the TagDict.
* tag will be converted to the PropertyMap.
*/
TagDict toDict() const;
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList &properties);
/*!
* Implements the unified tag dictionary interface -- import function.
* Implements the unified property interface -- import function.
* As with the export, only one tag is taken into account. If the file
* has no tag at all, APE will be created.
* has no tag at all, an APE tag will be created.
*/
void fromDict(const TagDict &);
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the MPC::Properties for this file. If no audio properties

View File

@ -238,6 +238,28 @@ void UserTextIdentificationFrame::setDescription(const String &s)
TextIdentificationFrame::setText(l);
}
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.
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());
else
map.insert(key, l);
return map;
}
UserTextIdentificationFrame *UserTextIdentificationFrame::find(
ID3v2::Tag *tag, const String &description) // static
{

View File

@ -236,6 +236,11 @@ namespace TagLib {
void setText(const String &text);
void setText(const StringList &fields);
/*!
* Reimplement function.
*/
PropertyMap asProperties() const;
/*!
* Searches for the user defined text frame with the description \a description
* in \a tag. This returns null if no matching frames were found.

View File

@ -262,6 +262,142 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc
return checkEncoding(fields, encoding, header()->version());
}
static const uint frameTranslationSize = 55;
static const char *frameTranslation[][2] = {
// Text information frames
{ "TALB", "ALBUM"},
{ "TBPM", "BPM" },
{ "TCOM", "COMPOSER" },
{ "TCON", "GENRE" },
{ "TCOP", "COPYRIGHT" },
{ "TDEN", "ENCODINGTIME" },
{ "TDLY", "PLAYLISTDELAY" },
{ "TDOR", "ORIGINALDATE" },
{ "TDRC", "DATE" },
// { "TRDA", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TDAT", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TYER", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
// { "TIME", "DATE" }, // id3 v2.3, replaced by TDRC in v2.4
{ "TDRL", "RELEASEDATE" },
{ "TDTG", "TAGGINGDATE" },
{ "TENC", "ENCODEDBY" },
{ "TEXT", "LYRICIST" },
{ "TFLT", "FILETYPE" },
{ "TIPL", "INVOLVEDPEOPLE" },
{ "TIT1", "CONTENTGROUP" },
{ "TIT2", "TITLE"},
{ "TIT3", "SUBTITLE" },
{ "TKEY", "INITIALKEY" },
{ "TLAN", "LANGUAGE" },
{ "TLEN", "LENGTH" },
{ "TMCL", "MUSICIANCREDITS" },
{ "TMED", "MEDIATYPE" },
{ "TMOO", "MOOD" },
{ "TOAL", "ORIGINALALBUM" },
{ "TOFN", "ORIGINALFILENAME" },
{ "TOLY", "ORIGINALLYRICIST" },
{ "TOPE", "ORIGINALARTIST" },
{ "TOWN", "OWNER" },
{ "TPE1", "ARTIST"},
{ "TPE2", "ALBUMARTIST" }, // id3's spec says 'PERFORMER', but most programs use 'ALBUMARTIST'
{ "TPE3", "CONDUCTOR" },
{ "TPE4", "REMIXER" }, // could also be ARRANGER
{ "TPOS", "DISCNUMBER" },
{ "TPRO", "PRODUCEDNOTICE" },
{ "TPUB", "PUBLISHER" },
{ "TRCK", "TRACKNUMBER" },
{ "TRSN", "RADIOSTATION" },
{ "TRSO", "RADIOSTATIONOWNER" },
{ "TSOA", "ALBUMSORT" },
{ "TSOP", "ARTISTSORT" },
{ "TSOT", "TITLESORT" },
{ "TSO2", "ALBUMARTISTSORT" }, // non-standard, used by iTunes
{ "TSRC", "ISRC" },
{ "TSSE", "ENCODING" },
// URL frames
{ "WCOP", "COPYRIGHTURL" },
{ "WOAF", "FILEWEBPAGE" },
{ "WOAR", "ARTISTWEBPAGE" },
{ "WOAS", "AUDIOSOURCEWEBPAGE" },
{ "WORS", "RADIOSTATIONWEBPAGE" },
{ "WPAY", "PAYMENTWEBPAGE" },
{ "WPUB", "PUBLISHERWEBPAGE" },
{ "WXXX", "URL"},
// Other frames
{ "COMM", "COMMENT" },
{ "USLT", "LYRICS" },
};
Map<ByteVector, String> &idMap()
{
static Map<ByteVector, String> m;
if(m.isEmpty())
for(size_t i = 0; i < frameTranslationSize; ++i)
m[frameTranslation[i][0]] = frameTranslation[i][1];
return m;
}
// list of deprecated frames and their successors
static const uint deprecatedFramesSize = 4;
static const char *deprecatedFrames[][2] = {
{"TRDA", "TDRC"}, // 2.3 -> 2.4 (http://en.wikipedia.org/wiki/ID3)
{"TDAT", "TDRC"}, // 2.3 -> 2.4
{"TYER", "TDRC"}, // 2.3 -> 2.4
{"TIME", "TDRC"}, // 2.3 -> 2.4
};
FrameIDMap &deprecationMap()
{
static FrameIDMap depMap;
if(depMap.isEmpty())
for(uint i = 0; i < deprecatedFramesSize; ++i)
depMap[deprecatedFrames[i][0]] = deprecatedFrames[i][1];
return depMap;
}
String Frame::frameIDToKey(const ByteVector &id)
{
Map<ByteVector, String> &m = idMap();
if(m.contains(id))
return m[id];
if(deprecationMap().contains(id))
return m[deprecationMap()[id]];
return String::null;
}
ByteVector Frame::keyToFrameID(const String &s)
{
static Map<String, ByteVector> m;
if(m.isEmpty())
for(size_t i = 0; i < frameTranslationSize; ++i)
m[frameTranslation[i][1]] = frameTranslation[i][0];
if(m.contains(s.upper()))
return m[s];
return ByteVector::null;
}
PropertyMap Frame::asProperties() const
{
const ByteVector &id = frameID();
// workaround until this function is virtual
if(id == "TXXX")
return dynamic_cast< const UserTextIdentificationFrame* >(this)->asProperties();
else if(id[0] == 'T')
return dynamic_cast< const TextIdentificationFrame* >(this)->asProperties();
else if(id == "WXXX")
return dynamic_cast< const UserUrlLinkFrame* >(this)->asProperties();
else if(id[0] == 'W')
return dynamic_cast< const UrlLinkFrame* >(this)->asProperties();
else if(id == "COMM")
return dynamic_cast< const CommentsFrame* >(this)->asProperties();
else if(id == "USLT")
return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties();
else {
PropertyMap m;
m.unsupportedData().append(id);
return m;
}
}
////////////////////////////////////////////////////////////////////////////////
// Frame::Header class
////////////////////////////////////////////////////////////////////////////////

View File

@ -222,6 +222,27 @@ namespace TagLib {
String::Type checkTextEncoding(const StringList &fields,
String::Type encoding) const;
/*!
* Parses the contents of this frame as PropertyMap. If that fails, the returend
* PropertyMap will be empty, and its unsupportedData() will contain this frame's
* ID.
* BIC: Will be a virtual function in future releases.
*/
PropertyMap asProperties() const;
/*!
* Returns an appropriate ID3 frame ID for the given free-form tag key. This method
* will return ByteVector::null if no specialized translation is found.
*/
static ByteVector keyToFrameID(const String &);
/*!
* Returns a free-form tag name for the given ID3 frame ID. Note that this does not work
* for general frame IDs such as TXXX or WXXX; in such a case String::null is returned.
*/
static String frameIDToKey(const ByteVector &);
private:
Frame(const Frame &);
Frame &operator=(const Frame &);

View File

@ -31,17 +31,9 @@
#include "id3v2extendedheader.h"
#include "id3v2footer.h"
#include "id3v2synchdata.h"
#include "id3v2dicttools.h"
#include "tbytevector.h"
#include "id3v1genres.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;
@ -334,25 +326,19 @@ void ID3v2::Tag::removeFrames(const ByteVector &id)
removeFrame(*it, true);
}
TagDict ID3v2::Tag::toDict(StringList *ignoreInfo) const
PropertyMap ID3v2::Tag::properties() const
{
TagDict dict;
FrameList::ConstIterator frameIt = frameList().begin();
for (; frameIt != frameList().end(); ++frameIt) {
ByteVector id = (*frameIt)->frameID();
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);
if (ignoreInfo)
ignoreInfo->append(String(id) + " frame is not supported");
} else if (deprecated(id)) {
debug("toDict() found deprecated id3 frame: " + id);
if (ignoreInfo)
ignoreInfo->append(String(id) + " frame is deprecated");
} else {
// in the future, something like dict[frame->tagName()].append(frame->values())
// might replace the following lines.
KeyValuePair kvp = parseFrame(*frameIt);
KeyValuePair kvp = parseFrame(*it);
dict[kvp.first].append(kvp.second);
}
}

View File

@ -261,22 +261,26 @@ namespace TagLib {
void removeFrames(const ByteVector &id);
/*!
* Implements the unified tag dictionary interface -- export function.
* Implements the unified property interface -- export function.
* This function does some work to translate the hard-specified ID3v2
* frame types into a free-form string-to-stringlist dictionary.
* frame types into a free-form string-to-stringlist PropertyMap.
*
* If the optional pointer to a StringList is given, that list will
* be filled with a descriptive text for each ID3v2 frame that could
* not be incorporated into the dict interface (binary data, unsupported
* frames, ...).
*/
TagDict toDict(StringList *ignoredInfo = 0) const;
PropertyMap properties() const;
/*!
* Implements the unified tag dictionary interface -- import function.
* See the comments in toDict().
* Removes unsupported frames given by \a properties. The elements of
* \a properties must be taken from properties().unsupportedData() and
* are the four-byte frame IDs of ID3 frames which are not compatible
* with the PropertyMap schema.
*/
void fromDict(const TagDict &);
void removeUnsupportedProperties(const StringList &properties);
/*!
* Implements the unified property interface -- import function.
* See the comments in properties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Render the tag back to binary data, suitable to be written to disk.

View File

@ -133,29 +133,38 @@ TagLib::Tag *MPEG::File::tag() const
return &d->tag;
}
TagLib::TagDict MPEG::File::toDict(void) const
PropertyMap MPEG::File::properties() const
{
// once Tag::toDict() is virtual, this case distinction could actually be done
// once Tag::properties() is virtual, this case distinction could actually be done
// within TagUnion.
if (d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->toDict();
if (d->hasAPE)
return d->tag.access<APE::Tag>(APEIndex, false)->toDict();
if (d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->toDict();
return TagLib::TagDict();
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->properties();
if(d->hasAPE)
return d->tag.access<APE::Tag>(APEIndex, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->properties();
return PropertyMap();
}
void MPEG::File::fromDict(const TagDict &dict)
void MPEG::File::removeUnsupportedProperties(const StringList &properties)
{
if (d->hasID3v2)
d->tag.access<ID3v2::Tag>(ID3v2Index, false)->fromDict(dict);
else if (d->hasAPE)
d->tag.access<APE::Tag>(APEIndex, false)->fromDict(dict);
else if (d->hasID3v1)
d->tag.access<ID3v1::Tag>(ID3v1Index, false)->fromDict(dict);
if(d->hasID3v2)
d->tag.access<ID3v2::Tag>(ID3v2Index, false)->removeUnsupportedProperties(properties);
else if(d->hasAPE)
d->tag.access<APE::Tag>(APEIndex, false)->removeUnsupportedProperties(properties);
else if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(ID3v1Index, false)->removeUnsupportedProperties(properties);
}
PropertyMap MPEG::File::setProperties(const PropertyMap &properties)
{
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->setProperties(properties);
else if(d->hasAPE)
return d->tag.access<APE::Tag>(APEIndex, false)->setProperties(properties);
else if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->setProperties(properties);
else
d->tag.access<ID3v2::Tag>(ID3v2Index, true)->fromDict(dict);
return d->tag.access<ID3v2::Tag>(ID3v2Index, true)->setProperties(properties);
}
MPEG::Properties *MPEG::File::audioProperties() const

View File

@ -130,19 +130,21 @@ namespace TagLib {
virtual Tag *tag() const;
/*!
* Implements the unified tag dictionary interface -- export function.
* If the file contains more than one tag (e.g. ID3v2 and v1), only the
* Implements the unified property interface -- export function.
* If the file contains more than one tag, only the
* first one (in the order ID3v2, APE, ID3v1) will be converted to the
* TagDict.
* PropertyMap.
*/
TagDict toDict() const;
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList &properties);
/*!
* Implements the unified tag dictionary interface -- import function.
* As with the export, only one tag is taken into account. If the file
* has no tag at all, ID3v2 will be created.
*/
void fromDict(const TagDict &);
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the MPEG::Properties for this file. If no audio properties

View File

@ -23,13 +23,12 @@
using namespace TagLib;
typedef Map<String,StringList> supertype;
PropertyMap::PropertyMap() : Map<String,StringList>()
PropertyMap::PropertyMap() : SimplePropertyMap()
{
}
PropertyMap::PropertyMap(const PropertyMap &m) : Map<String,StringList>(m)
PropertyMap::PropertyMap(const PropertyMap &m) : SimplePropertyMap(m)
{
}
@ -43,11 +42,11 @@ bool PropertyMap::insert(const String &key, const StringList &values)
if(realKey.isNull())
return false;
Iterator result = supertype::find(realKey);
Iterator result = SimplePropertyMap::find(realKey);
if(result == end())
supertype::insert(realKey, values);
SimplePropertyMap::insert(realKey, values);
else
supertype::operator[](realKey).append(values);
SimplePropertyMap::operator[](realKey).append(values);
return true;
}
@ -56,8 +55,8 @@ bool PropertyMap::replace(const String &key, const StringList &values)
String realKey = prepareKey(key);
if(realKey.isNull())
return false;
supertype::erase(realKey);
supertype::insert(realKey, values);
SimplePropertyMap::erase(realKey);
SimplePropertyMap::insert(realKey, values);
return true;
}
@ -66,7 +65,7 @@ PropertyMap::Iterator PropertyMap::find(const String &key)
String realKey = prepareKey(key);
if(realKey.isNull())
return end();
return supertype::find(realKey);
return SimplePropertyMap::find(realKey);
}
PropertyMap::ConstIterator PropertyMap::find(const String &key) const
@ -74,40 +73,46 @@ PropertyMap::ConstIterator PropertyMap::find(const String &key) const
String realKey = prepareKey(key);
if(realKey.isNull())
return end();
return supertype::find(realKey);
return SimplePropertyMap::find(realKey);
}
bool PropertyMap::contains(const String &key) const
{
String realKey = prepareKey(key);
// we consider keys with empty value list as not present
if(realKey.isNull() || supertype::operator[](realKey).isEmpty())
if(realKey.isNull() || SimplePropertyMap::operator[](realKey).isEmpty())
return false;
return supertype::contains(realKey);
return SimplePropertyMap::contains(realKey);
}
/*!
* Erase the \a key and its values from the map.
*/
PropertyMap &PropertyMap::erase(const String &key)
{
String realKey = prepareKey(key);
if (realKey.isNull())
if(realKey.isNull())
return *this;
supertype::erase(realKey);
SimplePropertyMap::erase(realKey);
return *this;
}
PropertyMap &PropertyMap::merge(const PropertyMap &other)
{
for(PropertyMap::ConstIterator it = other.begin(); it != other.end(); ++it) {
insert(it->first, it->second);
}
unsupported.append(other.unsupported);
return *this;
}
const StringList &PropertyMap::operator[](const String &key) const
{
String realKey = prepareKey(key);
return supertype::operator[](realKey);
return SimplePropertyMap::operator[](realKey);
}
StringList &PropertyMap::operator[](const String &key)
{
String realKey = prepareKey(key);
return supertype::operator[](realKey);
return SimplePropertyMap::operator[](realKey);
}
void PropertyMap::removeEmpty()
@ -125,6 +130,11 @@ StringList &PropertyMap::unsupportedData()
return unsupported;
}
const StringList &PropertyMap::unsupportedData() const
{
return unsupported;
}
static String PropertyMap::prepareKey(const String &proposed) {
if(proposed.isEmpty())
return String::null;

View File

@ -27,6 +27,7 @@
namespace TagLib {
typedef Map<String,StringList> SimplePropertyMap;
//! A map for format-independent <key,valuelist> tag representations.
@ -46,12 +47,12 @@ namespace TagLib {
*
*/
class TAGLIB_EXPORT PropertyMap: public Map<String,StringList>
class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap
{
public:
typedef Map<String,StringList>::Iterator Iterator;
typedef Map<String,StringList>::ConstIterator ConstIterator;
typedef SimplePropertyMap::Iterator Iterator;
typedef SimplePropertyMap::ConstIterator ConstIterator;
PropertyMap();
@ -95,6 +96,14 @@ namespace TagLib {
*/
PropertyMap &erase(const String &key);
/*!
* Merge the contents of \a other into this PropertyMap.
* If a key is contained in both maps, the values of the second
* are appended to that of the first.
* The unsupportedData() lists are concatenated as well.
*/
PropertyMap &merge(const PropertyMap &other);
/*!
* Returns a reference to the value associated with \a key.
*
@ -121,6 +130,7 @@ namespace TagLib {
* same PropertyMap as argument.
*/
StringList &unsupportedData();
const StringList &unsupportedData() const;
/*!
* Removes all entries which have an empty value list.