This commit is contained in:
Lukáš Lalinský 2012-03-17 10:41:02 +01:00
commit 76222cb1eb
61 changed files with 2332 additions and 7 deletions

View File

@ -48,6 +48,7 @@ set(tag_HDRS
toolkit/tfilestream.h
toolkit/tmap.h
toolkit/tmap.tcc
toolkit/tpropertymap.h
mpeg/mpegfile.h
mpeg/mpegproperties.h
mpeg/mpegheader.h
@ -275,6 +276,7 @@ set(toolkit_SRCS
toolkit/tfile.cpp
toolkit/tfilestream.cpp
toolkit/tdebug.cpp
toolkit/tpropertymap.cpp
toolkit/unicode.cpp
)

View File

@ -36,6 +36,7 @@
#include <tdebug.h>
#include <tagunion.h>
#include <id3v1tag.h>
#include <tpropertymap.h>
#include "apefile.h"
@ -109,6 +110,33 @@ TagLib::Tag *APE::File::tag() const
return &d->tag;
}
PropertyMap APE::File::properties() const
{
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 APE::File::removeUnsupportedProperties(const StringList &properties)
{
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 APE::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>(APEIndex, true)->setProperties(properties);
}
APE::Properties *APE::File::audioProperties() const
{
return d->properties;

View File

@ -110,6 +110,25 @@ namespace TagLib {
*/
virtual TagLib::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* If the file contains both an APE and an ID3v1 tag, only APE
* will be converted to the PropertyMap.
*/
PropertyMap properties() const;
/*!
* Removes unsupported properties. Forwards to the actual Tag's
* removeUnsupportedProperties() function.
*/
void removeUnsupportedProperties(const StringList &properties);
/*!
* Implements the unified property interface -- import function.
* As for the export, only one tag is taken into account. If the file
* has no tag at all, APE will be created.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the APE::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -34,6 +34,7 @@
#include <tfile.h>
#include <tstring.h>
#include <tmap.h>
#include <tpropertymap.h>
#include "apetag.h"
#include "apefooter.h"
@ -174,6 +175,86 @@ void APE::Tag::setTrack(uint i)
addValue("TRACK", String::number(i), true);
}
// conversions of tag keys between what we use in PropertyMap and what's usual
// for APE tags
static const uint keyConversionsSize = 5; //usual, APE
static const char *keyConversions[][2] = {{"TRACKNUMBER", "TRACK" },
{"DATE", "YEAR" },
{"ALBUMARTIST", "ALBUM ARTIST"},
{"DISCNUMBER", "DISC" },
{"REMIXER", "MIXARTIST" }};
PropertyMap APE::Tag::properties() const
{
PropertyMap properties;
ItemListMap::ConstIterator it = itemListMap().begin();
for(; it != itemListMap().end(); ++it) {
String tagName = PropertyMap::prepareKey(it->first);
// if the item is Binary or Locator, or if the key is an invalid string,
// add to unsupportedData
if(it->second.type() != Item::Text || tagName.isNull())
properties.unsupportedData().append(it->first);
else {
// Some tags need to be handled specially
for(uint i = 0; i < keyConversionsSize; ++i)
if(tagName == keyConversions[i][1])
tagName = keyConversions[i][0];
properties[tagName].append(it->second.toStringList());
}
}
return properties;
}
void APE::Tag::removeUnsupportedProperties(const StringList &properties)
{
StringList::ConstIterator it = properties.begin();
for(; it != properties.end(); ++it)
removeItem(*it);
}
PropertyMap APE::Tag::setProperties(const PropertyMap &origProps)
{
PropertyMap properties(origProps); // make a local copy that can be modified
// see comment in properties()
for(uint i = 0; i < keyConversionsSize; ++i)
if(properties.contains(keyConversions[i][0])) {
properties.insert(keyConversions[i][1], properties[keyConversions[i][0]]);
properties.erase(keyConversions[i][0]);
}
// first check if tags need to be removed completely
StringList toRemove;
ItemListMap::ConstIterator remIt = itemListMap().begin();
for(; remIt != itemListMap().end(); ++remIt) {
String key = PropertyMap::prepareKey(remIt->first);
// only remove if a) key is valid, b) type is text, c) key not contained in new properties
if(!key.isNull() && remIt->second.type() == APE::Item::Text && !properties.contains(key))
toRemove.append(remIt->first);
}
for (StringList::Iterator removeIt = toRemove.begin(); removeIt != toRemove.end(); removeIt++)
removeItem(*removeIt);
// now sync in the "forward direction"
PropertyMap::ConstIterator it = properties.begin();
for(; it != properties.end(); ++it) {
const String &tagName = it->first;
if(!(itemListMap().contains(tagName)) || !(itemListMap()[tagName].values() == it->second)) {
if(it->second.size() == 0)
removeItem(tagName);
else {
StringList::ConstIterator valueIt = it->second.begin();
addValue(tagName, *valueIt, true);
++valueIt;
for(; valueIt != it->second.end(); ++valueIt)
addValue(tagName, *valueIt, false);
}
}
}
return PropertyMap();
}
APE::Footer *APE::Tag::footer() const
{
return &d->footer;

View File

@ -103,6 +103,30 @@ namespace TagLib {
virtual void setYear(uint i);
virtual void setTrack(uint i);
/*!
* Implements the unified tag dictionary interface -- export function.
* APE tags are perfectly compatible with the dictionary interface because they
* support both arbitrary tag names and multiple values. Currently only
* APE items of type *Text* are handled by the dictionary interface; all *Binary*
* and *Locator* items will be put into the unsupportedData list and can be
* deleted on request using removeUnsupportedProperties(). The same happens
* to Text items if their key is invalid for PropertyMap (which should actually
* never happen).
*
* The only conversion done by this export function is to rename the APE tags
* TRACK to TRACKNUMBER, YEAR to DATE, and ALBUM ARTIST to ALBUMARTIST, respectively,
* in order to be compliant with the names used in other formats.
*/
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList &properties);
/*!
* Implements the unified tag dictionary interface -- import function. The same
* comments as for the export function apply.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns a pointer to the tag's footer.
*/

View File

@ -28,6 +28,7 @@
#include <tlist.h>
#include <tdebug.h>
#include <tagunion.h>
#include <tpropertymap.h>
#include <id3v2header.h>
#include <id3v2tag.h>
@ -138,6 +139,41 @@ TagLib::Tag *FLAC::File::tag() const
return &d->tag;
}
PropertyMap FLAC::File::properties() const
{
// once Tag::properties() is virtual, this case distinction could actually be done
// within TagUnion.
if(d->hasXiphComment)
return d->tag.access<Ogg::XiphComment>(XiphIndex, false)->properties();
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->properties();
return PropertyMap();
}
void FLAC::File::removeUnsupportedProperties(const StringList &unsupported)
{
if(d->hasXiphComment)
d->tag.access<Ogg::XiphComment>(XiphIndex, false)->removeUnsupportedProperties(unsupported);
if(d->hasID3v2)
d->tag.access<ID3v2::Tag>(ID3v2Index, false)->removeUnsupportedProperties(unsupported);
if(d->hasID3v1)
d->tag.access<ID3v1::Tag>(ID3v1Index, false)->removeUnsupportedProperties(unsupported);
}
PropertyMap FLAC::File::setProperties(const PropertyMap &properties)
{
if(d->hasXiphComment)
return d->tag.access<Ogg::XiphComment>(XiphIndex, false)->setProperties(properties);
else if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->setProperties(properties);
else if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->setProperties(properties);
else
return d->tag.access<Ogg::XiphComment>(XiphIndex, true)->setProperties(properties);
}
FLAC::Properties *FLAC::File::audioProperties() const
{
return d->properties;

View File

@ -29,6 +29,7 @@
#include "taglib_export.h"
#include "tfile.h"
#include "tlist.h"
#include "tag.h"
#include "flacpicture.h"
#include "flacproperties.h"
@ -36,7 +37,6 @@
namespace TagLib {
class Tag;
namespace ID3v2 { class FrameFactory; class Tag; }
namespace ID3v1 { class Tag; }
namespace Ogg { class XiphComment; }
@ -118,6 +118,23 @@ namespace TagLib {
*/
virtual TagLib::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* If the file contains more than one tag (e.g. XiphComment and ID3v1),
* only the first one (in the order XiphComment, ID3v2, ID3v1) will be
* converted to the PropertyMap.
*/
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList &);
/*!
* 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, a XiphComment will be created.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the FLAC::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -23,6 +23,7 @@
#include "itfile.h"
#include "tdebug.h"
#include "modfileprivate.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace IT;
@ -65,6 +66,16 @@ Mod::Tag *IT::File::tag() const
return &d->tag;
}
PropertyMap IT::File::properties() const
{
return d->tag.properties();
}
PropertyMap IT::File::setProperties(const PropertyMap &properties)
{
return d->tag.setProperties(properties);
}
IT::Properties *IT::File::audioProperties() const
{
return &d->properties;

View File

@ -60,6 +60,18 @@ namespace TagLib {
Mod::Tag *tag() const;
/*!
* Forwards to Mod::Tag::properties().
* BIC: will be removed once File::toDict() is made virtual
*/
PropertyMap properties() const;
/*!
* Forwards to Mod::Tag::setProperties().
* BIC: will be removed once File::setProperties() is made virtual
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the IT::Properties for this file. If no audio properties
* were read then this will return a null pointer.
@ -74,6 +86,7 @@ namespace TagLib {
*/
bool save();
private:
File(const File &);
File &operator=(const File &);

View File

@ -23,6 +23,7 @@
#include "tstringlist.h"
#include "tdebug.h"
#include "modfileprivate.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace Mod;
@ -70,6 +71,16 @@ Mod::Properties *Mod::File::audioProperties() const
return &d->properties;
}
PropertyMap Mod::File::properties() const
{
return d->tag.properties();
}
PropertyMap Mod::File::setProperties(const PropertyMap &properties)
{
return d->tag.setProperties(properties);
}
bool Mod::File::save()
{
if(readOnly()) {

View File

@ -61,6 +61,17 @@ namespace TagLib {
Mod::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* Forwards to Mod::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* Forwards to Mod::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the Mod::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -20,6 +20,8 @@
***************************************************************************/
#include "modtag.h"
#include "tstringlist.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace Mod;
@ -120,3 +122,47 @@ void Mod::Tag::setTrackerName(const String &trackerName)
{
d->trackerName = trackerName;
}
PropertyMap Mod::Tag::properties() const
{
PropertyMap properties;
properties["TITLE"] = d->title;
properties["COMMENT"] = d->comment;
if(!(d->trackerName.isNull()))
properties["TRACKERNAME"] = d->trackerName;
return properties;
}
PropertyMap Mod::Tag::setProperties(const PropertyMap &origProps)
{
PropertyMap properties(origProps);
properties.removeEmpty();
StringList oneValueSet;
if(properties.contains("TITLE")) {
d->title = properties["TITLE"].front();
oneValueSet.append("TITLE");
} else
d->title = String::null;
if(properties.contains("COMMENT")) {
d->comment = properties["COMMENT"].front();
oneValueSet.append("COMMENT");
} else
d->comment = String::null;
if(properties.contains("TRACKERNAME")) {
d->trackerName = properties["TRACKERNAME"].front();
oneValueSet.append("TRACKERNAME");
} else
d->trackerName = String::null;
// for each tag that has been set above, remove the first entry in the corresponding
// value list. The others will be returned as unsupported by this format.
for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
if(properties[*it].size() == 1)
properties.erase(*it);
else
properties[*it].erase( properties[*it].begin() );
}
return properties;
}

View File

@ -159,6 +159,22 @@ namespace TagLib {
*/
void setTrackerName(const String &trackerName);
/*!
* Implements the unified property interface -- export function.
* Since the module tag is very limited, the exported map is as well.
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* Because of the limitations of the module file tag, any tags besides
* COMMENT, TITLE and, if it is an XM file, TRACKERNAME, will be
* returened. Additionally, if the map contains tags with multiple values,
* all but the first will be contained in the returned map of unsupported
* properties.
*/
PropertyMap setProperties(const PropertyMap &);
private:
Tag(const Tag &);
Tag &operator=(const Tag &);

View File

@ -27,6 +27,7 @@
#include <tstring.h>
#include <tagunion.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include "mpcfile.h"
#include "id3v1tag.h"
@ -113,6 +114,34 @@ TagLib::Tag *MPC::File::tag() const
return &d->tag;
}
PropertyMap MPC::File::properties() const
{
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::removeUnsupportedProperties(const StringList &properties)
{
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

@ -28,6 +28,7 @@
#include "taglib_export.h"
#include "tfile.h"
#include "tag.h"
#include "mpcproperties.h"
@ -107,6 +108,22 @@ namespace TagLib {
*/
virtual TagLib::Tag *tag() const;
/*!
* 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 PropertyMap.
*/
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList &properties);
/*!
* 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, an APE tag will be created.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the MPC::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -29,6 +29,7 @@
#include <tstringlist.h>
#include "commentsframe.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace ID3v2;
@ -109,6 +110,19 @@ void CommentsFrame::setTextEncoding(String::Type encoding)
d->textEncoding = encoding;
}
PropertyMap CommentsFrame::asProperties() const
{
String key = PropertyMap::prepareKey(description());
PropertyMap map;
if(key.isEmpty() || key == "COMMENT")
map.insert("COMMENT", text());
else if(key.isNull())
map.unsupportedData().append(L"COMM/" + description());
else
map.insert("COMMENT:" + 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 with a single key.
* - if description() is empty or "COMMENT", 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.
* - otherwise, the key will be "COMMENT:<description>"
* - The single value will be the frame's text().
*/
PropertyMap asProperties() 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

@ -25,8 +25,9 @@
#include <tbytevectorlist.h>
#include <id3v2tag.h>
#include "textidentificationframe.h"
#include "tpropertymap.h"
#include "id3v1genres.h"
using namespace TagLib;
using namespace ID3v2;
@ -57,6 +58,32 @@ TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data) :
setData(data);
}
TextIdentificationFrame *TextIdentificationFrame::createTIPLFrame(const PropertyMap &properties) // static
{
TextIdentificationFrame *frame = new TextIdentificationFrame("TIPL");
StringList l;
for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){
l.append(it->first);
l.append(it->second.toString(",")); // comma-separated list of names
}
frame->setText(l);
return frame;
}
TextIdentificationFrame *TextIdentificationFrame::createTMCLFrame(const PropertyMap &properties) // static
{
TextIdentificationFrame *frame = new TextIdentificationFrame("TMCL");
StringList l;
for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it){
if(!it->first.startsWith(instrumentPrefix)) // should not happen
continue;
l.append(it->first.substr(instrumentPrefix.size()));
l.append(it->second.toString(","));
}
frame->setText(l);
return frame;
}
TextIdentificationFrame::~TextIdentificationFrame()
{
delete d;
@ -92,6 +119,61 @@ void TextIdentificationFrame::setTextEncoding(String::Type encoding)
d->textEncoding = encoding;
}
// 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"},
};
const KeyConversionMap &TextIdentificationFrame::involvedPeopleMap() // static
{
static KeyConversionMap m;
if(m.isEmpty())
for(uint i = 0; i < involvedPeopleSize; ++i)
m.insert(involvedPeople[i][1], involvedPeople[i][0]);
return m;
}
PropertyMap TextIdentificationFrame::asProperties() const
{
if(frameID() == "TIPL")
return makeTIPLProperties();
if(frameID() == "TMCL")
return makeTMCLProperties();
PropertyMap map;
String tagName = frameIDToKey(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] = ' ';
}
}
PropertyMap ret;
ret.insert(tagName, values);
return ret;
}
////////////////////////////////////////////////////////////////////////////////
// TextIdentificationFrame protected members
////////////////////////////////////////////////////////////////////////////////
@ -170,6 +252,55 @@ TextIdentificationFrame::TextIdentificationFrame(const ByteVector &data, Header
parseFields(fieldData(data));
}
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;
}
StringList l = fieldList();
for(StringList::ConstIterator it = l.begin(); it != l.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;
}
StringList l = fieldList();
for(StringList::ConstIterator it = l.begin(); it != l.end(); ++it) {
String instrument = PropertyMap::prepareKey(*it);
if(instrument.isNull()) {
// instrument is not a valid key -> frame unsupported
map.clear();
map.unsupportedData().append(frameID());
return map;
}
map.insert(L"PERFORMER:" + instrument, (++it)->split(","));
}
return map;
}
////////////////////////////////////////////////////////////////////////////////
// UserTextIdentificationFrame public members
////////////////////////////////////////////////////////////////////////////////
@ -191,6 +322,14 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(const ByteVector &data)
checkFields();
}
UserTextIdentificationFrame::UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding) :
TextIdentificationFrame("TXXX", encoding),
d(0)
{
setDescription(description);
setText(values);
}
String UserTextIdentificationFrame::toString() const
{
return "[" + description() + "] " + fieldList().toString();
@ -238,6 +377,23 @@ void UserTextIdentificationFrame::setDescription(const String &s)
TextIdentificationFrame::setText(l);
}
PropertyMap UserTextIdentificationFrame::asProperties() const
{
String tagName = description();
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(L"TXXX/" + description());
else {
StringList v = fieldList();
for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it)
if(*it != description())
map.insert(key, *it);
}
return map;
}
UserTextIdentificationFrame *UserTextIdentificationFrame::find(
ID3v2::Tag *tag, const String &description) // static
{

View File

@ -36,6 +36,7 @@ namespace TagLib {
namespace ID3v2 {
class Tag;
typedef Map<String, String> KeyConversionMap;
//! An ID3v2 text identification frame implementation
@ -123,6 +124,20 @@ namespace TagLib {
*/
explicit TextIdentificationFrame(const ByteVector &data);
/*!
* This is a special factory method to create a TIPL (involved people list)
* frame from the given \a properties. Will parse key=[list of values] data
* into the TIPL format as specified in the ID3 standard.
*/
static TextIdentificationFrame *createTIPLFrame(const PropertyMap &properties);
/*!
* This is a special factory method to create a TMCL (musician credits list)
* frame from the given \a properties. Will parse key=[list of values] data
* into the TMCL format as specified in the ID3 standard, where key should be
* of the form instrumentPrefix:instrument.
*/
static TextIdentificationFrame *createTMCLFrame(const PropertyMap &properties);
/*!
* Destroys this TextIdentificationFrame instance.
*/
@ -173,6 +188,14 @@ namespace TagLib {
*/
StringList fieldList() const;
/*!
* Returns a KeyConversionMap mapping a role as it would be used in a PropertyMap
* to the corresponding key used in a TIPL ID3 frame to describe that role.
*/
static const KeyConversionMap &involvedPeopleMap();
PropertyMap asProperties() const;
protected:
// Reimplementations.
@ -188,6 +211,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;
};
@ -218,6 +251,12 @@ namespace TagLib {
*/
explicit UserTextIdentificationFrame(const ByteVector &data);
/*!
* Creates a user defined text identification frame with the given \a description
* and \a values.
*/
UserTextIdentificationFrame(const String &description, const StringList &values, String::Type encoding = String::UTF8);
virtual String toString() const;
/*!
@ -236,6 +275,21 @@ namespace TagLib {
void setText(const String &text);
void setText(const StringList &fields);
/*!
* 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;
/*!
* 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

@ -27,7 +27,9 @@
#include "unsynchronizedlyricsframe.h"
#include <tbytevectorlist.h>
#include <id3v2tag.h>
#include <tdebug.h>
#include <tpropertymap.h>
using namespace TagLib;
using namespace ID3v2;
@ -111,6 +113,30 @@ void UnsynchronizedLyricsFrame::setTextEncoding(String::Type encoding)
d->textEncoding = encoding;
}
PropertyMap UnsynchronizedLyricsFrame::asProperties() const
{
PropertyMap map;
String key = PropertyMap::prepareKey(description());
if(key.isEmpty() || key.upper() == "LYRICS")
map.insert("LYRICS", text());
else if(key.isNull())
map.unsupportedData().append(L"USLT/" + description());
else
map.insert("LYRICS:" + key, text());
return map;
}
UnsynchronizedLyricsFrame *UnsynchronizedLyricsFrame::findByDescription(const ID3v2::Tag *tag, const String &d) // static
{
ID3v2::FrameList lyrics = tag->frameList("USLT");
for(ID3v2::FrameList::ConstIterator it = lyrics.begin(); it != lyrics.end(); ++it){
UnsynchronizedLyricsFrame *frame = dynamic_cast<UnsynchronizedLyricsFrame *>(*it);
if(frame && frame->description() == d)
return frame;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

View File

@ -134,6 +134,28 @@ namespace TagLib {
*/
void setTextEncoding(String::Type encoding);
/*! Parses this frame as PropertyMap with a single key.
* - if description() is empty or "LYRICS", the key will be "LYRICS"
* - if description() is not a valid PropertyMap key, the frame will be
* marked unsupported by an entry "USLT/<description>" in the unsupportedData()
* attribute of the returned map.
* - otherwise, the key will be "LYRICS:<description>"
* - The single value will be the frame's text().
* Note that currently the language() field is not supported by the PropertyMap
* interface.
*/
PropertyMap asProperties() const;
/*!
* LyricsFrames each have a unique description. This searches for a lyrics
* frame with the decription \a d and returns a pointer to it. If no
* frame is found that matches the given description null is returned.
*
* \see description()
*/
static UnsynchronizedLyricsFrame *findByDescription(const Tag *tag, const String &d);
protected:
// Reimplementations.

View File

@ -26,8 +26,10 @@
***************************************************************************/
#include "urllinkframe.h"
#include "id3v2tag.h"
#include <tdebug.h>
#include <tstringlist.h>
#include <tpropertymap.h>
using namespace TagLib;
using namespace ID3v2;
@ -78,6 +80,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 +153,30 @@ void UserUrlLinkFrame::setDescription(const String &s)
d->description = s;
}
PropertyMap UserUrlLinkFrame::asProperties() const
{
PropertyMap map;
String key = PropertyMap::prepareKey(description());
if(key.isEmpty() || key.upper() == "URL")
map.insert("URL", url());
else if(key.isNull())
map.unsupportedData().append(L"WXXX/" + description());
else
map.insert("URL:" + key, url());
return map;
}
UserUrlLinkFrame *UserUrlLinkFrame::find(ID3v2::Tag *tag, const String &description) // static
{
FrameList l = tag->frameList("WXXX");
for(FrameList::Iterator it = l.begin(); it != l.end(); ++it) {
UserUrlLinkFrame *f = dynamic_cast<UserUrlLinkFrame *>(*it);
if(f && f->description() == description)
return f;
}
return 0;
}
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,22 @@ 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;
/*!
* Searches for the user defined url frame with the description \a description
* in \a tag. This returns null if no matching frames were found.
*/
static UserUrlLinkFrame *find(Tag *tag, const String &description);
protected:
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;

View File

@ -38,6 +38,12 @@
#include "id3v2frame.h"
#include "id3v2synchdata.h"
#include "tpropertymap.h"
#include "frames/textidentificationframe.h"
#include "frames/urllinkframe.h"
#include "frames/unsynchronizedlyricsframe.h"
#include "frames/commentsframe.h"
#include "frames/unknownframe.h"
using namespace TagLib;
using namespace ID3v2;
@ -95,10 +101,56 @@ ByteVector Frame::textDelimiter(String::Type t)
return d;
}
const String Frame::instrumentPrefix("PERFORMER:");
const String Frame::commentPrefix("COMMENT:");
const String Frame::lyricsPrefix("LYRICS:");
const String Frame::urlPrefix("URL:");
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
Frame *Frame::createTextualFrame(const String &key, const StringList &values) //static
{
// check if the key is contained in the key<=>frameID mapping
ByteVector frameID = keyToFrameID(key);
if(!frameID.isNull()) {
if(frameID[0] == 'T'){ // text frame
TextIdentificationFrame *frame = new TextIdentificationFrame(frameID, String::UTF8);
frame->setText(values);
return frame;
} else if(values.size() == 1){ // URL frame (not WXXX); support only one value
UrlLinkFrame* frame = new UrlLinkFrame(frameID);
frame->setUrl(values.front());
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){
UnsynchronizedLyricsFrame *frame = new UnsynchronizedLyricsFrame();
frame->setDescription(key == "LYRICS" ? key : key.substr(lyricsPrefix.size()));
frame->setText(values.front());
return frame;
}
// -URL: depending on the number of values, use WXXX or TXXX (with description=URL)
if((key == "URL" || key.startsWith(urlPrefix)) && values.size() == 1){
UserUrlLinkFrame *frame = new UserUrlLinkFrame(String::UTF8);
frame->setDescription(key == "URL" ? key : key.substr(urlPrefix.size()));
frame->setUrl(values.front());
return frame;
}
// -COMMENT: depending on the number of values, use COMM or TXXX (with description=COMMENT)
if((key == "COMMENT" || key.startsWith(commentPrefix)) && values.size() == 1){
CommentsFrame *frame = new CommentsFrame(String::UTF8);
frame->setDescription(key == "COMMENT" ? key : key.substr(commentPrefix.size()));
frame->setText(values.front());
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);
}
Frame::~Frame()
{
delete d;
@ -262,6 +314,163 @@ String::Type Frame::checkTextEncoding(const StringList &fields, String::Type enc
return checkEncoding(fields, encoding, header()->version());
}
static const uint frameTranslationSize = 51;
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" }, handled separately
{ "TIT1", "CONTENTGROUP" },
{ "TIT2", "TITLE"},
{ "TIT3", "SUBTITLE" },
{ "TKEY", "INITIALKEY" },
{ "TLAN", "LANGUAGE" },
{ "TLEN", "LENGTH" },
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
{ "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"}, handled specially
// Other frames
{ "COMM", "COMMENT" },
//{ "USLT", "LYRICS" }, handled specially
};
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
};
Map<ByteVector,ByteVector> &deprecationMap()
{
static Map<ByteVector,ByteVector> 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
{
if(dynamic_cast< const UnknownFrame *>(this)) {
PropertyMap m;
m.unsupportedData().append("UNKNOWN/" + frameID());
return m;
}
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();
PropertyMap m;
m.unsupportedData().append(id);
return m;
}
void Frame::splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties,
PropertyMap &tiplProperties, PropertyMap &tmclProperties)
{
singleFrameProperties.clear();
tiplProperties.clear();
tmclProperties.clear();
for(PropertyMap::ConstIterator it = original.begin(); it != original.end(); ++it) {
if(TextIdentificationFrame::involvedPeopleMap().contains(it->first))
tiplProperties.insert(it->first, it->second);
else if(it->first.startsWith(TextIdentificationFrame::instrumentPrefix))
tmclProperties.insert(it->first, it->second);
else
singleFrameProperties.insert(it->first, it->second);
}
}
////////////////////////////////////////////////////////////////////////////////
// Frame::Header class
////////////////////////////////////////////////////////////////////////////////

View File

@ -33,6 +33,7 @@
namespace TagLib {
class StringList;
class PropertyMap;
namespace ID3v2 {
@ -56,6 +57,14 @@ namespace TagLib {
friend class FrameFactory;
public:
/*!
* Creates a textual frame which corresponds to a single key in the PropertyMap
* interface. These are all (User)TextIdentificationFrames except TIPL and TMCL,
* all (User)URLLinkFrames, CommentsFrames, and UnsynchronizedLyricsFrame.
*/
static Frame *createTextualFrame(const String &key, const StringList &values);
/*!
* Destroys this Frame instance.
*/
@ -126,6 +135,28 @@ namespace TagLib {
*/
static ByteVector textDelimiter(String::Type t);
/*!
* The string with which an instrument name is prefixed to build a key in a PropertyMap;
* used to translate PropertyMaps to TMCL frames. In the current implementation, this
* is "PERFORMER:".
*/
static const String instrumentPrefix;
/*!
* The PropertyMap key prefix which triggers the use of a COMM frame instead of a TXXX
* frame for a non-standard key. In the current implementation, this is "COMMENT:".
*/
static const String commentPrefix;
/*!
* The PropertyMap key prefix which triggers the use of a USLT frame instead of a TXXX
* frame for a non-standard key. In the current implementation, this is "LYRICS:".
*/
static const String lyricsPrefix;
/*!
* The PropertyMap key prefix which triggers the use of a WXXX frame instead of a TXX
* frame for a non-standard key. In the current implementation, this is "URL:".
*/
static const String urlPrefix;
protected:
class Header;
@ -222,6 +253,44 @@ 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 &);
/*!
* This helper function splits the PropertyMap \a original into three ProperytMaps
* \a singleFrameProperties, \a tiplProperties, and \a tmclProperties, such that:
* - \a singleFrameProperties contains only of keys which can be represented with
* exactly one ID3 frame per key. In the current implementation
* this is everything except for the fixed "involved people" keys and keys of the
* form "TextIdentificationFrame::instrumentPrefix" + "instrument", which are
* mapped to a TMCL frame.
* - \a tiplProperties will consist of those keys that are present in
* TextIdentificationFrame::involvedPeopleMap()
* - \a tmclProperties contains the "musician credits" keys which should be mapped
* to a TMCL frame
*/
static void splitProperties(const PropertyMap &original, PropertyMap &singleFrameProperties,
PropertyMap &tiplProperties, PropertyMap &tmclProperties);
private:
Frame(const Frame &);
Frame &operator=(const Frame &);

View File

@ -24,18 +24,23 @@
***************************************************************************/
#include <tfile.h>
#include <tdebug.h>
#include "id3v2tag.h"
#include "id3v2header.h"
#include "id3v2extendedheader.h"
#include "id3v2footer.h"
#include "id3v2synchdata.h"
#include "tbytevector.h"
#include "id3v1genres.h"
#include "tpropertymap.h"
#include <tdebug.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;
@ -324,9 +329,99 @@ void ID3v2::Tag::removeFrame(Frame *frame, bool del)
void ID3v2::Tag::removeFrames(const ByteVector &id)
{
FrameList l = d->frameListMap[id];
for(FrameList::Iterator it = l.begin(); it != l.end(); ++it)
removeFrame(*it, true);
FrameList l = d->frameListMap[id];
for(FrameList::Iterator it = l.begin(); it != l.end(); ++it)
removeFrame(*it, true);
}
PropertyMap ID3v2::Tag::properties() const
{
PropertyMap properties;
for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) {
PropertyMap props = (*it)->asProperties();
properties.merge(props);
}
return properties;
}
void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties)
{
for(StringList::ConstIterator it = properties.begin(); it != properties.end(); ++it){
if(it->startsWith("UNKNOWN/")) {
String frameID = it->substr(String("UNKNOWN/").size());
if(frameID.size() != 4)
continue; // invalid specification
ByteVector id = frameID.data(String::Latin1);
// delete all unknown frames of given type
FrameList l = frameList(id);
for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++)
if (dynamic_cast<const UnknownFrame *>(*fit) != NULL)
removeFrame(*fit);
} else if(it->size() == 4){
ByteVector id = it->data(String::Latin1);
removeFrames(id);
} else {
ByteVector id = it->substr(0,4).data(String::Latin1);
if(it->size() <= 5)
continue; // invalid specification
String description = it->substr(5);
Frame *frame;
if(id == "TXXX")
frame = UserTextIdentificationFrame::find(this, description);
else if(id == "WXXX")
frame = UserUrlLinkFrame::find(this, description);
else if(id == "COMM")
frame = CommentsFrame::findByDescription(this, description);
else if(id == "USLT")
frame = UnsynchronizedLyricsFrame::findByDescription(this, description);
if(frame)
removeFrame(frame);
}
}
}
PropertyMap ID3v2::Tag::setProperties(const PropertyMap &origProps)
{
FrameList framesToDelete;
// we split up the PropertyMap into the "normal" keys and the "complicated" ones,
// which are those according to TIPL or TMCL frames.
PropertyMap properties;
PropertyMap tiplProperties;
PropertyMap tmclProperties;
Frame::splitProperties(origProps, properties, tiplProperties, tmclProperties);
for(FrameListMap::ConstIterator it = frameListMap().begin(); it != frameListMap().end(); ++it){
for(FrameList::ConstIterator lit = it->second.begin(); lit != it->second.end(); ++lit){
PropertyMap frameProperties = (*lit)->asProperties();
if(it->first == "TIPL") {
if (tiplProperties != frameProperties)
framesToDelete.append(*lit);
else
tiplProperties.erase(frameProperties);
} else if(it->first == "TMCL") {
if (tmclProperties != frameProperties)
framesToDelete.append(*lit);
else
tmclProperties.erase(frameProperties);
} else if(!properties.contains(frameProperties))
framesToDelete.append(*lit);
else
properties.erase(frameProperties);
}
}
for(FrameList::ConstIterator it = framesToDelete.begin(); it != framesToDelete.end(); ++it)
removeFrame(*it);
// now create remaining frames:
// start with the involved people list (TIPL)
if(!tiplProperties.isEmpty())
addFrame(TextIdentificationFrame::createTIPLFrame(tiplProperties));
// proceed with the musician credit list (TMCL)
if(!tmclProperties.isEmpty())
addFrame(TextIdentificationFrame::createTMCLFrame(tmclProperties));
// now create the "one key per frame" frames
for(PropertyMap::ConstIterator it = properties.begin(); it != properties.end(); ++it)
addFrame(Frame::createTextualFrame(it->first, it->second));
return PropertyMap(); // ID3 implements the complete PropertyMap interface, so an empty map is returned
}
ByteVector ID3v2::Tag::render() const

View File

@ -260,6 +260,56 @@ namespace TagLib {
*/
void removeFrames(const ByteVector &id);
/*!
* 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 PropertyMap:
* - if ID3v2 frame ID is known by Frame::frameIDToKey(), the returned
* key is used
* - if the frame ID is "TXXX" (user text frame), the description() is
* used as key
* - if the frame ID is "WXXX" (user url frame),
* - if the description is empty or "URL", the key "URL" is used
* - otherwise, the key "URL:<description>" is used;
* - if the frame ID is "COMM" (comments frame),
* - if the description is empty or "COMMENT", the key "COMMENT"
* is used
* - otherwise, the key "COMMENT:<description>" is used;
* - if the frame ID is "USLT" (unsynchronized lyrics),
* - if the description is empty or "LYRICS", the key "LYRICS" is used
* - otherwise, the key "LYRICS:<description>" is used;
* - if the frame ID is "TIPL" (involved peoples list), and if all the
* roles defined in the frame are known in TextIdentificationFrame::involvedPeopleMap(),
* then "<role>=<name>" will be contained in the returned obejct for each
* - if the frame ID is "TMCL" (musician credit list), then
* "PERFORMER:<instrument>=<name>" will be contained in the returned
* PropertyMap for each defined musician
* In any other case, the unsupportedData() of the returned object will contain
* the frame's ID and, in case of a frame ID which is allowed to appear more than
* once, the description, separated by a "/".
*
*/
PropertyMap properties() const;
/*!
* Removes unsupported frames given by \a properties. The elements of
* \a properties must be taken from properties().unsupportedData(); they
* are of one of the following forms:
* - a four-character frame ID, if the ID3 specification allows only one
* frame with that ID (thus, the frame is uniquely determined)
* - frameID + "/" + description(), when the ID is one of "TXXX", "WXXX",
* "COMM", or "USLT",
* - "UNKNOWN/" + frameID, for frames that could not be parsed by TagLib.
* In that case, *all* unknown frames with the given ID will be removed.
*/
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

@ -35,6 +35,7 @@
#include "mpegfile.h"
#include "mpegheader.h"
#include "tpropertymap.h"
using namespace TagLib;
@ -133,6 +134,40 @@ TagLib::Tag *MPEG::File::tag() const
return &d->tag;
}
PropertyMap MPEG::File::properties() const
{
// 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)->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::removeUnsupportedProperties(const StringList &properties)
{
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
return d->tag.access<ID3v2::Tag>(ID3v2Index, true)->setProperties(properties);
}
MPEG::Properties *MPEG::File::audioProperties() const
{
return d->properties;

View File

@ -28,6 +28,7 @@
#include "taglib_export.h"
#include "tfile.h"
#include "tag.h"
#include "mpegproperties.h"
@ -128,6 +129,23 @@ namespace TagLib {
*/
virtual Tag *tag() const;
/*!
* 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
* PropertyMap.
*/
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.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the MPEG::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -27,9 +27,11 @@
#include <tstring.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include "vorbisfile.h"
using namespace TagLib;
class Vorbis::File::FilePrivate
@ -85,6 +87,16 @@ Ogg::XiphComment *Vorbis::File::tag() const
return d->comment;
}
PropertyMap Vorbis::File::properties() const
{
return d->comment->properties();
}
PropertyMap Vorbis::File::setProperties(const PropertyMap &properties)
{
return d->comment->setProperties(properties);
}
Vorbis::Properties *Vorbis::File::audioProperties() const
{
return d->properties;

View File

@ -90,6 +90,19 @@ namespace TagLib {
*/
virtual Ogg::XiphComment *tag() const;
/*!
* Implements the unified property interface -- export function.
* This forwards directly to XiphComment::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified tag dictionary interface -- import function.
* Like properties(), this is a forwarder to the file's XiphComment.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the Vorbis::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -27,6 +27,7 @@
#include <tdebug.h>
#include <xiphcomment.h>
#include <tpropertymap.h>
using namespace TagLib;
@ -188,6 +189,44 @@ const Ogg::FieldListMap &Ogg::XiphComment::fieldListMap() const
return d->fieldListMap;
}
PropertyMap Ogg::XiphComment::properties() const
{
return d->fieldListMap;
}
PropertyMap Ogg::XiphComment::setProperties(const PropertyMap &properties)
{
// check which keys are to be deleted
StringList toRemove;
for(FieldListMap::ConstIterator it = d->fieldListMap.begin(); it != d->fieldListMap.end(); ++it)
if (!properties.contains(it->first))
toRemove.append(it->first);
for(StringList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it)
removeField(*it);
// now go through keys in \a properties and check that the values match those in the xiph comment */
PropertyMap::ConstIterator it = properties.begin();
for(; it != properties.end(); ++it)
{
if(!d->fieldListMap.contains(it->first) || !(it->second == d->fieldListMap[it->first])) {
const StringList &sl = it->second;
if(sl.size() == 0)
// zero size string list -> remove the tag with all values
removeField(it->first);
else {
// replace all strings in the list for the tag
StringList::ConstIterator valueIterator = sl.begin();
addField(it->first, *valueIterator, true);
++valueIterator;
for(; valueIterator != sl.end(); ++valueIterator)
addField(it->first, *valueIterator, false);
}
}
}
return PropertyMap();
}
String Ogg::XiphComment::vendorID() const
{
return d->vendorID;

View File

@ -140,6 +140,21 @@ namespace TagLib {
*/
const FieldListMap &fieldListMap() const;
/*!
* Implements the unified property interface -- export function.
* The result is a one-to-one match of the Xiph comment, since it is
* completely compatible with the property interface (in fact, a Xiph
* comment is nothing more than a map from tag names to list of values,
* as is the dict interface).
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* The tags from the given map will be stored one-to-one in the file.
*/
PropertyMap setProperties(const PropertyMap&);
/*!
* Returns the vendor ID of the Ogg Vorbis encoder. libvorbis 1.0 as the
* most common case always returns "Xiph.Org libVorbis I 20020717".

View File

@ -26,6 +26,8 @@
#include <tbytevector.h>
#include <tdebug.h>
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include "aifffile.h"
@ -83,6 +85,17 @@ ID3v2::Tag *RIFF::AIFF::File::tag() const
return d->tag;
}
PropertyMap RIFF::AIFF::File::properties() const
{
return d->tag->properties();
}
PropertyMap RIFF::AIFF::File::setProperties(const PropertyMap &properties)
{
return d->tag->setProperties(properties);
}
RIFF::AIFF::Properties *RIFF::AIFF::File::audioProperties() const
{
return d->properties;

View File

@ -83,6 +83,18 @@ namespace TagLib {
*/
virtual ID3v2::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* This method forwards to ID3v2::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* This method forwards to ID3v2::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the AIFF::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -26,6 +26,8 @@
#include <tbytevector.h>
#include <tdebug.h>
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include "wavfile.h"
@ -83,6 +85,17 @@ ID3v2::Tag *RIFF::WAV::File::tag() const
return d->tag;
}
PropertyMap RIFF::WAV::File::properties() const
{
return d->tag->properties();
}
PropertyMap RIFF::WAV::File::setProperties(const PropertyMap &properties)
{
return d->tag->setProperties(properties);
}
RIFF::WAV::Properties *RIFF::WAV::File::audioProperties() const
{
return d->properties;

View File

@ -83,6 +83,18 @@ namespace TagLib {
*/
virtual ID3v2::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* This method forwards to ID3v2::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* This method forwards to ID3v2::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the WAV::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -23,6 +23,7 @@
#include "tstringlist.h"
#include "tdebug.h"
#include "modfileprivate.h"
#include "tpropertymap.h"
#include <iostream>
@ -67,6 +68,16 @@ Mod::Tag *S3M::File::tag() const
return &d->tag;
}
PropertyMap S3M::File::properties() const
{
return d->tag.properties();
}
PropertyMap S3M::File::setProperties(const PropertyMap &properties)
{
return d->tag.setProperties(properties);
}
S3M::Properties *S3M::File::audioProperties() const
{
return &d->properties;

View File

@ -60,6 +60,18 @@ namespace TagLib {
Mod::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* Forwards to Mod::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* Forwards to Mod::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the S3M::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -24,6 +24,8 @@
***************************************************************************/
#include "tag.h"
#include "tstringlist.h"
#include "tpropertymap.h"
using namespace TagLib;
@ -53,6 +55,101 @@ bool Tag::isEmpty() const
track() == 0);
}
PropertyMap Tag::properties() const
{
PropertyMap map;
if(!(title().isNull()))
map["TITLE"].append(title());
if(!(artist().isNull()))
map["ARTIST"].append(artist());
if(!(album().isNull()))
map["ALBUM"].append(album());
if(!(comment().isNull()))
map["COMMENT"].append(comment());
if(!(genre().isNull()))
map["GENRE"].append(genre());
if(!(year() == 0))
map["DATE"].append(String::number(year()));
if(!(track() == 0))
map["TRACKNUMBER"].append(String::number(track()));
return map;
}
void Tag::removeUnsupportedProperties(const StringList&)
{
}
PropertyMap Tag::setProperties(const PropertyMap &origProps)
{
PropertyMap properties(origProps);
properties.removeEmpty();
StringList oneValueSet;
// can this be simplified by using some preprocessor defines / function pointers?
if(properties.contains("TITLE")) {
setTitle(properties["TITLE"].front());
oneValueSet.append("TITLE");
} else
setTitle(String::null);
if(properties.contains("ARTIST")) {
setArtist(properties["ARTIST"].front());
oneValueSet.append("ARTIST");
} else
setArtist(String::null);
if(properties.contains("ALBUM")) {
setAlbum(properties["ALBUM"].front());
oneValueSet.append("ALBUM");
} else
setAlbum(String::null);
if(properties.contains("COMMENT")) {
setComment(properties["COMMENT"].front());
oneValueSet.append("COMMENT");
} else
setComment(String::null);
if(properties.contains("GENRE")) {
setGenre(properties["GENRE"].front());
oneValueSet.append("GENRE");
} else
setGenre(String::null);
if(properties.contains("DATE")) {
bool ok;
int date = properties["DATE"].front().toInt(&ok);
if(ok) {
setYear(date);
oneValueSet.append("DATE");
} else
setYear(0);
}
else
setYear(0);
if(properties.contains("TRACKNUMBER")) {
bool ok;
int track = properties["TRACKNUMBER"].front().toInt(&ok);
if(ok) {
setTrack(track);
oneValueSet.append("TRACKNUMBER");
} else
setTrack(0);
}
else
setYear(0);
// for each tag that has been set above, remove the first entry in the corresponding
// value list. The others will be returned as unsupported by this format.
for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
if(properties[*it].size() == 1)
properties.erase(*it);
else
properties[*it].erase( properties[*it].begin() );
}
return properties;
}
void Tag::duplicate(const Tag *source, Tag *target, bool overwrite) // static
{
if(overwrite) {

View File

@ -41,6 +41,8 @@ namespace TagLib {
* in TagLib::AudioProperties, TagLib::File and TagLib::FileRef.
*/
class PropertyMap;
class TAGLIB_EXPORT Tag
{
public:
@ -50,6 +52,32 @@ namespace TagLib {
*/
virtual ~Tag();
/*!
* Exports the tags of the file as dictionary mapping (human readable) tag
* names (Strings) to StringLists of tag values.
* The default implementation in this class considers only the usual built-in
* tags (artist, album, ...) and only one value per key.
*/
PropertyMap properties() const;
/*!
* Removes unsupported properties, or a subset of them, from the tag.
* The parameter \a properties must contain only entries from
* properties().unsupportedData().
* BIC: Will become virtual in future releases. Currently the non-virtual
* standard implementation of TagLib::Tag does nothing, since there are
* no unsupported elements.
*/
void removeUnsupportedProperties(const StringList& properties);
/*!
* Sets the tags of this File to those specified in \a properties. This default
* implementation sets only the tags for which setter methods exist in this class
* (artist, album, ...), and only one value per key; the rest will be contained
* in the returned PropertyMap.
*/
PropertyMap setProperties(const PropertyMap &properties);
/*!
* Returns the track name; if no track name is present in the tag
* String::null will be returned.

View File

@ -24,6 +24,7 @@
***************************************************************************/
#include "tagunion.h"
#include "tstringlist.h"
using namespace TagLib;

View File

@ -27,6 +27,7 @@
#include "tfilestream.h"
#include "tstring.h"
#include "tdebug.h"
#include "tpropertymap.h"
#include <stdio.h>
#include <string.h>
@ -50,6 +51,24 @@
# define W_OK 2
#endif
#include "asffile.h"
#include "mpegfile.h"
#include "vorbisfile.h"
#include "flacfile.h"
#include "oggflacfile.h"
#include "mpcfile.h"
#include "mp4file.h"
#include "wavpackfile.h"
#include "speexfile.h"
#include "trueaudiofile.h"
#include "aifffile.h"
#include "wavfile.h"
#include "apefile.h"
#include "modfile.h"
#include "s3mfile.h"
#include "itfile.h"
#include "xmfile.h"
using namespace TagLib;
class File::FilePrivate
@ -95,6 +114,118 @@ FileName File::name() const
return d->stream->name();
}
PropertyMap File::properties() const
{
// ugly workaround until this method is virtual
if(dynamic_cast<const APE::File* >(this))
return dynamic_cast<const APE::File* >(this)->properties();
if(dynamic_cast<const FLAC::File* >(this))
return dynamic_cast<const FLAC::File* >(this)->properties();
if(dynamic_cast<const IT::File* >(this))
return dynamic_cast<const IT::File* >(this)->properties();
if(dynamic_cast<const Mod::File* >(this))
return dynamic_cast<const Mod::File* >(this)->properties();
if(dynamic_cast<const MPC::File* >(this))
return dynamic_cast<const MPC::File* >(this)->properties();
if(dynamic_cast<const MPEG::File* >(this))
return dynamic_cast<const MPEG::File* >(this)->properties();
if(dynamic_cast<const Ogg::FLAC::File* >(this))
return dynamic_cast<const Ogg::FLAC::File* >(this)->properties();
if(dynamic_cast<const Ogg::Speex::File* >(this))
return dynamic_cast<const Ogg::Speex::File* >(this)->properties();
if(dynamic_cast<const Ogg::Vorbis::File* >(this))
return dynamic_cast<const Ogg::Vorbis::File* >(this)->properties();
if(dynamic_cast<const RIFF::AIFF::File* >(this))
return dynamic_cast<const RIFF::AIFF::File* >(this)->properties();
if(dynamic_cast<const RIFF::WAV::File* >(this))
return dynamic_cast<const RIFF::WAV::File* >(this)->properties();
if(dynamic_cast<const S3M::File* >(this))
return dynamic_cast<const S3M::File* >(this)->properties();
if(dynamic_cast<const TrueAudio::File* >(this))
return dynamic_cast<const TrueAudio::File* >(this)->properties();
if(dynamic_cast<const WavPack::File* >(this))
return dynamic_cast<const WavPack::File* >(this)->properties();
if(dynamic_cast<const XM::File* >(this))
return dynamic_cast<const XM::File* >(this)->properties();
// no specialized implementation available -> use generic one
// - ASF: ugly format, largely undocumented, not worth implementing
// dict interface ...
// - MP4: taglib's MP4::Tag does not really support anything beyond
// the basic implementation, therefor we use just the default Tag
// interface
return tag()->properties();
}
void File::removeUnsupportedProperties(const StringList &properties)
{
// here we only consider those formats that could possibly contain
// unsupported properties
if(dynamic_cast<APE::File* >(this))
dynamic_cast<APE::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<FLAC::File* >(this))
dynamic_cast<FLAC::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<MPC::File* >(this))
dynamic_cast<MPC::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<MPEG::File* >(this))
dynamic_cast<MPEG::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<Ogg::FLAC::File* >(this))
dynamic_cast<Ogg::FLAC::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<Ogg::Speex::File* >(this))
dynamic_cast<Ogg::Speex::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<Ogg::Vorbis::File* >(this))
dynamic_cast<Ogg::Vorbis::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<RIFF::AIFF::File* >(this))
dynamic_cast<RIFF::AIFF::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<RIFF::WAV::File* >(this))
dynamic_cast<RIFF::WAV::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<S3M::File* >(this))
dynamic_cast<S3M::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<TrueAudio::File* >(this))
dynamic_cast<TrueAudio::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<WavPack::File* >(this))
dynamic_cast<WavPack::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<XM::File* >(this))
dynamic_cast<XM::File* >(this)->removeUnsupportedProperties(properties);
else
tag()->removeUnsupportedProperties(properties);
}
PropertyMap File::setProperties(const PropertyMap &properties)
{
if(dynamic_cast<APE::File* >(this))
return dynamic_cast<APE::File* >(this)->setProperties(properties);
else if(dynamic_cast<FLAC::File* >(this))
return dynamic_cast<FLAC::File* >(this)->setProperties(properties);
else if(dynamic_cast<IT::File* >(this))
return dynamic_cast<IT::File* >(this)->setProperties(properties);
else if(dynamic_cast<Mod::File* >(this))
return dynamic_cast<Mod::File* >(this)->setProperties(properties);
else if(dynamic_cast<MPC::File* >(this))
return dynamic_cast<MPC::File* >(this)->setProperties(properties);
else if(dynamic_cast<MPEG::File* >(this))
return dynamic_cast<MPEG::File* >(this)->setProperties(properties);
else if(dynamic_cast<Ogg::FLAC::File* >(this))
return dynamic_cast<Ogg::FLAC::File* >(this)->setProperties(properties);
else if(dynamic_cast<Ogg::Speex::File* >(this))
return dynamic_cast<Ogg::Speex::File* >(this)->setProperties(properties);
else if(dynamic_cast<Ogg::Vorbis::File* >(this))
return dynamic_cast<Ogg::Vorbis::File* >(this)->setProperties(properties);
else if(dynamic_cast<RIFF::AIFF::File* >(this))
return dynamic_cast<RIFF::AIFF::File* >(this)->setProperties(properties);
else if(dynamic_cast<RIFF::WAV::File* >(this))
return dynamic_cast<RIFF::WAV::File* >(this)->setProperties(properties);
else if(dynamic_cast<S3M::File* >(this))
return dynamic_cast<S3M::File* >(this)->setProperties(properties);
else if(dynamic_cast<TrueAudio::File* >(this))
return dynamic_cast<TrueAudio::File* >(this)->setProperties(properties);
else if(dynamic_cast<WavPack::File* >(this))
return dynamic_cast<WavPack::File* >(this)->setProperties(properties);
else if(dynamic_cast<XM::File* >(this))
return dynamic_cast<XM::File* >(this)->setProperties(properties);
else
return tag()->setProperties(properties);
}
ByteVector File::readBlock(ulong length)
{
return d->stream->readBlock(length);

View File

@ -28,6 +28,7 @@
#include "taglib_export.h"
#include "taglib.h"
#include "tag.h"
#include "tbytevector.h"
#include "tiostream.h"
@ -36,6 +37,7 @@ namespace TagLib {
class String;
class Tag;
class AudioProperties;
class PropertyMap;
//! A file class with some useful methods for tag manipulation
@ -76,6 +78,36 @@ namespace TagLib {
*/
virtual Tag *tag() const = 0;
/*!
* Exports the tags of the file as dictionary mapping (human readable) tag
* names (Strings) to StringLists of tag values. Calls the according specialization
* in the File subclasses.
* For each metadata object of the file that could not be parsed into the PropertyMap
* format, the returend map's unsupportedData() list will contain one entry identifying
* that object (e.g. the frame type for ID3v2 tags). Use removeUnsupportedProperties()
* to remove (a subset of) them.
* BIC: Will be made virtual in future releases.
*/
PropertyMap properties() const;
/*!
* Removes unsupported properties, or a subset of them, from the file's metadata.
* The parameter \a properties must contain only entries from
* properties().unsupportedData().
* BIC: Will be mad virtual in future releases.
*/
void removeUnsupportedProperties(const StringList& properties);
/*!
* Sets the tags of this File to those specified in \a properties. Calls the
* according specialization method in the subclasses of File to do the translation
* into the format-specific details.
* If some value(s) could not be written imported to the specific metadata format,
* the returned PropertyMap will contain those value(s). Otherwise it will be empty,
* indicating that no problems occured.
* BIC: will become pure virtual in the future
*/
PropertyMap setProperties(const PropertyMap &properties);
/*!
* Returns a pointer to this file's audio properties. This should be
* reimplemented in the concrete subclasses. If no audio properties were

View File

@ -227,6 +227,11 @@ namespace TagLib {
*/
bool operator==(const List<T> &l) const;
/*!
* Compares this list with \a l and returns true if the lists differ.
*/
bool operator!=(const List<T> &l) const;
protected:
/*
* If this List is being shared via implicit sharing, do a deep copy of the

View File

@ -300,6 +300,12 @@ bool List<T>::operator==(const List<T> &l) const
return d->list == l.d->list;
}
template <class T>
bool List<T>::operator!=(const List<T> &l) const
{
return d->list != l.d->list;
}
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,202 @@
/***************************************************************************
copyright : (C) 2012 by Michael Helmling
email : helmling@mathematik.uni-kl.de
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301 USA *
***************************************************************************/
#include "tpropertymap.h"
using namespace TagLib;
PropertyMap::PropertyMap() : SimplePropertyMap()
{
}
PropertyMap::PropertyMap(const PropertyMap &m) : SimplePropertyMap(m), unsupported(m.unsupported)
{
}
PropertyMap::PropertyMap(const SimplePropertyMap &m)
{
for(SimplePropertyMap::ConstIterator it = m.begin(); it != m.end(); ++it){
String key = prepareKey(it->first);
if(!key.isNull())
insert(it->first, it->second);
else
unsupported.append(it->first);
}
}
PropertyMap::~PropertyMap()
{
}
bool PropertyMap::insert(const String &key, const StringList &values)
{
String realKey = prepareKey(key);
if(realKey.isNull())
return false;
Iterator result = SimplePropertyMap::find(realKey);
if(result == end())
SimplePropertyMap::insert(realKey, values);
else
SimplePropertyMap::operator[](realKey).append(values);
return true;
}
bool PropertyMap::replace(const String &key, const StringList &values)
{
String realKey = prepareKey(key);
if(realKey.isNull())
return false;
SimplePropertyMap::erase(realKey);
SimplePropertyMap::insert(realKey, values);
return true;
}
PropertyMap::Iterator PropertyMap::find(const String &key)
{
String realKey = prepareKey(key);
if(realKey.isNull())
return end();
return SimplePropertyMap::find(realKey);
}
PropertyMap::ConstIterator PropertyMap::find(const String &key) const
{
String realKey = prepareKey(key);
if(realKey.isNull())
return end();
return SimplePropertyMap::find(realKey);
}
bool PropertyMap::contains(const String &key) const
{
String realKey = prepareKey(key);
if(realKey.isNull())
return false;
return SimplePropertyMap::contains(realKey);
}
bool PropertyMap::contains(const PropertyMap &other) const
{
for(ConstIterator it = other.begin(); it != other.end(); ++it) {
if(!SimplePropertyMap::contains(it->first))
return false;
if ((*this)[it->first] != it->second)
return false;
}
return true;
}
PropertyMap &PropertyMap::erase(const String &key)
{
String realKey = prepareKey(key);
if(!realKey.isNull())
SimplePropertyMap::erase(realKey);
return *this;
}
PropertyMap &PropertyMap::erase(const PropertyMap &other)
{
for(ConstIterator it = other.begin(); it != other.end(); ++it)
erase(it->first);
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 SimplePropertyMap::operator[](realKey);
}
StringList &PropertyMap::operator[](const String &key)
{
String realKey = prepareKey(key);
return SimplePropertyMap::operator[](realKey);
}
bool PropertyMap::operator==(const PropertyMap &other) const
{
for(ConstIterator it = other.begin(); it != other.end(); ++it) {
ConstIterator thisFind = find(it->first);
if( thisFind == end() || (thisFind->second != it->second) )
return false;
}
for(ConstIterator it = begin(); it != end(); ++it) {
ConstIterator otherFind = other.find(it->first);
if( otherFind == other.end() || (otherFind->second != it->second) )
return false;
}
return unsupported == other.unsupported;
}
bool PropertyMap::operator!=(const PropertyMap &other) const
{
return !(*this == other);
}
String PropertyMap::toString() const
{
String ret = "";
for(ConstIterator it = begin(); it != end(); ++it)
ret += it->first+"="+it->second.toString(", ") + "\n";
if(!unsupported.isEmpty())
ret += "Unsupported Data: " + unsupported.toString(", ") + "\n";
return ret;
}
void PropertyMap::removeEmpty()
{
StringList emptyKeys;
for(Iterator it = begin(); it != end(); ++it)
if(it->second.isEmpty())
emptyKeys.append(it->first);
for(StringList::Iterator emptyIt = emptyKeys.begin(); emptyIt != emptyKeys.end(); emptyIt++ )
erase(*emptyIt);
}
StringList &PropertyMap::unsupportedData()
{
return unsupported;
}
const StringList &PropertyMap::unsupportedData() const
{
return unsupported;
}
String PropertyMap::prepareKey(const String &proposed) {
if(proposed.isEmpty())
return String::null;
for (String::ConstIterator it = proposed.begin(); it != proposed.end(); it++)
// forbid non-printable, non-ascii, '=' (#61) and '~' (#126)
if (*it < 32 || *it >= 128 || *it == 61 || *it == 126)
return String::null;
return proposed.upper();
}

View File

@ -0,0 +1,184 @@
/***************************************************************************
copyright : (C) 2012 by Michael Helmling
email : helmling@mathematik.uni-kl.de
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* This library is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this library; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301 USA *
***************************************************************************/
#ifndef PROPERTYMAP_H_
#define PROPERTYMAP_H_
#include "tmap.h"
#include "tstringlist.h"
namespace TagLib {
typedef Map<String,StringList> SimplePropertyMap;
//! A map for format-independent <key,valuelist> tag representations.
/*!
* 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.
* 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.
*
* In order to be safe with other formats, keep these additional restrictions in mind:
*
* - APE only allows keys from 2 to 16 printable ASCII characters (including space),
* with the exception of these strings: ID3, TAG, OggS, MP+
*
*/
class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap
{
public:
typedef SimplePropertyMap::Iterator Iterator;
typedef SimplePropertyMap::ConstIterator ConstIterator;
PropertyMap();
PropertyMap(const PropertyMap &m);
/*!
* Creates a PropertyMap initialized from a SimplePropertyMap. Copies all
* entries from \a m that have valid keys.
* Invalid keys will be appended to the unsupportedData() list.
*/
PropertyMap(const SimplePropertyMap &m);
virtual ~PropertyMap();
/*!
* Inserts \a values under \a key in the map. If \a key already exists,
* then \values will be appended to the existing StringList.
* The returned value indicates success, i.e. whether \a key is a
* valid key.
*/
bool insert(const String &key, const StringList &values);
/*!
* Replaces any existing values for \a key with the given \a values,
* and simply insert them if \a key did not exist before.
* The returned value indicates success, i.e. whether \a key is a
* valid key.
*/
bool replace(const String &key, const StringList &values);
/*!
* Find the first occurrence of \a key.
*/
Iterator find(const String &key);
/*!
* Find the first occurrence of \a key.
*/
ConstIterator find(const String &key) const;
/*!
* Returns true if the map contains values for \a key.
*/
bool contains(const String &key) const;
/*!
* Returns true if this map contains all keys of \a other
* and the values coincide for that keys. Does not take
* the unsupportedData list into account.
*/
bool contains(const PropertyMap &other) const;
/*!
* Erase the \a key and its values from the map.
*/
PropertyMap &erase(const String &key);
/*!
* Erases from this map all keys that appear in \a other.
*/
PropertyMap &erase(const PropertyMap &other);
/*!
* 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.
*
* \note: This has undefined behavior if the key is not valid or not
* present in the map.
*/
const StringList &operator[](const String &key) const;
/*!
* Returns a reference to the value associated with \a key.
*
* \note: This has undefined behavior if the key is not valid or not
* present in the map.
*/
StringList &operator[](const String &key);
/*!
* Returns true if and only if \other has the same contents as this map.
*/
bool operator==(const PropertyMap &other) const;
/*!
* Returns false if and only \other has the same contents as this map.
*/
bool operator!=(const PropertyMap &other) const;
/*!
* If a PropertyMap is read from a File object using File::properties(),
* the StringList returned from this function will represent metadata
* that could not be parsed into the PropertyMap representation. This could
* be e.g. binary data, unknown ID3 frames, etc.
* You can remove items from the returned list, which tells TagLib to remove
* those unsupported elements if you call File::setProperties() with the
* same PropertyMap as argument.
*/
StringList &unsupportedData();
const StringList &unsupportedData() const;
/*!
* Removes all entries which have an empty value list.
*/
void removeEmpty();
String toString() const;
/*!
* Converts \a proposed into another String suitable to be used as
* a key, or returns String::null if this is not possible.
*/
static String prepareKey(const String &proposed);
private:
StringList unsupported;
};
}
#endif /* PROPERTYMAP_H_ */

View File

@ -31,6 +31,8 @@
#include <tstring.h>
#include <tdebug.h>
#include <tagunion.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include "trueaudiofile.h"
#include "id3v1tag.h"
@ -126,6 +128,27 @@ TagLib::Tag *TrueAudio::File::tag() const
return &d->tag;
}
PropertyMap TrueAudio::File::properties() const
{
// 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)->properties();
if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->properties();
return PropertyMap();
}
PropertyMap TrueAudio::File::setProperties(const PropertyMap &properties)
{
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->setProperties(properties);
else if(d->hasID3v1)
return d->tag.access<ID3v1::Tag>(ID3v1Index, false)->setProperties(properties);
else
return d->tag.access<ID3v2::Tag>(ID3v2Index, true)->setProperties(properties);
}
TrueAudio::Properties *TrueAudio::File::audioProperties() const
{
return d->properties;

View File

@ -124,6 +124,20 @@ namespace TagLib {
*/
virtual TagLib::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* If the file contains both ID3v1 and v2 tags, only ID3v2 will be
* converted to the PropertyMap.
*/
PropertyMap properties() const;
/*!
* 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, ID3v2 will be created.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the TrueAudio::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -31,6 +31,7 @@
#include <tstring.h>
#include <tdebug.h>
#include <tagunion.h>
#include <tpropertymap.h>
#include "wavpackfile.h"
#include "id3v1tag.h"
@ -105,6 +106,25 @@ TagLib::Tag *WavPack::File::tag() const
return &d->tag;
}
PropertyMap WavPack::File::properties() const
{
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();
}
PropertyMap WavPack::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);
}
WavPack::Properties *WavPack::File::audioProperties() const
{
return d->properties;

View File

@ -106,6 +106,20 @@ namespace TagLib {
*/
virtual TagLib::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* If the file contains both an APE and an ID3v1 tag, only APE
* will be converted to the PropertyMap.
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* As for the export, only one tag is taken into account. If the file
* has no tag at all, APE will be created.
*/
PropertyMap setProperties(const PropertyMap&);
/*!
* Returns the MPC::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -23,6 +23,7 @@
#include "tdebug.h"
#include "xmfile.h"
#include "modfileprivate.h"
#include "tpropertymap.h"
#include <string.h>
#include <algorithm>
@ -379,6 +380,16 @@ Mod::Tag *XM::File::tag() const
return &d->tag;
}
PropertyMap XM::File::properties() const
{
return d->tag.properties();
}
PropertyMap XM::File::setProperties(const PropertyMap &properties)
{
return d->tag.setProperties(properties);
}
XM::Properties *XM::File::audioProperties() const
{
return &d->properties;

View File

@ -60,6 +60,18 @@ namespace TagLib {
Mod::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* Forwards to Mod::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* Forwards to Mod::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the XM::Properties for this file. If no audio properties
* were read then this will return a null pointer.

BIN
tests/data/rare_frames.mp3 Normal file

Binary file not shown.

BIN
tests/data/test.ogg Normal file

Binary file not shown.

View File

@ -4,7 +4,9 @@
#include <tag.h>
#include <tstringlist.h>
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <apetag.h>
#include <tdebug.h>
#include "utils.h"
using namespace std;
@ -15,6 +17,8 @@ class TestAPETag : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE(TestAPETag);
CPPUNIT_TEST(testIsEmpty);
CPPUNIT_TEST(testIsEmpty2);
CPPUNIT_TEST(testPropertyInterface1);
CPPUNIT_TEST(testPropertyInterface2);
CPPUNIT_TEST_SUITE_END();
public:
@ -35,6 +39,50 @@ public:
CPPUNIT_ASSERT(!tag.isEmpty());
}
void testPropertyInterface1()
{
APE::Tag tag;
PropertyMap dict = tag.properties();
CPPUNIT_ASSERT(dict.isEmpty());
dict["ARTIST"] = String("artist 1");
dict["ARTIST"].append("artist 2");
dict["TRACKNUMBER"].append("17");
tag.setProperties(dict);
CPPUNIT_ASSERT_EQUAL(String("17"), tag.itemListMap()["TRACK"].values()[0]);
CPPUNIT_ASSERT_EQUAL(2u, tag.itemListMap()["ARTIST"].values().size());
CPPUNIT_ASSERT_EQUAL(String("artist 1"), tag.artist());
CPPUNIT_ASSERT_EQUAL(17u, tag.track());
}
void testPropertyInterface2()
{
APE::Tag tag;
APE::Item item1 = APE::Item("TRACK", "17");
tag.setItem("TRACK", item1);
APE::Item item2 = APE::Item();
item2.setType(APE::Item::Binary);
tag.setItem("TESTBINARY", item2);
PropertyMap properties = tag.properties();
CPPUNIT_ASSERT_EQUAL(1u, properties.unsupportedData().size());
CPPUNIT_ASSERT(properties.contains("TRACKNUMBER"));
CPPUNIT_ASSERT(!properties.contains("TRACK"));
CPPUNIT_ASSERT(tag.itemListMap().contains("TESTBINARY"));
tag.removeUnsupportedProperties(properties.unsupportedData());
CPPUNIT_ASSERT(!tag.itemListMap().contains("TESTBINARY"));
APE::Item item3 = APE::Item("TRACKNUMBER", "29");
tag.setItem("TRACKNUMBER", item3);
properties = tag.properties();
CPPUNIT_ASSERT_EQUAL(2u, properties["TRACKNUMBER"].size());
CPPUNIT_ASSERT_EQUAL(String("17"), properties["TRACKNUMBER"][0]);
CPPUNIT_ASSERT_EQUAL(String("29"), properties["TRACKNUMBER"][1]);
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestAPETag);

View File

@ -4,6 +4,7 @@
#include <tag.h>
#include <tstringlist.h>
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <flacfile.h>
#include <xiphcomment.h>
#include "utils.h"
@ -22,6 +23,7 @@ class TestFLAC : public CppUnit::TestFixture
CPPUNIT_TEST(testRemoveAllPictures);
CPPUNIT_TEST(testRepeatedSave);
CPPUNIT_TEST(testSaveMultipleValues);
CPPUNIT_TEST(testDict);
CPPUNIT_TEST_SUITE_END();
public:
@ -208,6 +210,27 @@ public:
CPPUNIT_ASSERT_EQUAL(String("artist 2"), m["ARTIST"][1]);
}
void testDict()
{
// test unicode & multiple values with dict interface
ScopedFileCopy copy("silence-44-s", ".flac");
string newname = copy.fileName();
FLAC::File *f = new FLAC::File(newname.c_str());
PropertyMap dict;
dict["ARTIST"].append("artøst 1");
dict["ARTIST"].append("artöst 2");
f->setProperties(dict);
f->save();
delete f;
f = new FLAC::File(newname.c_str());
dict = f->properties();
CPPUNIT_ASSERT_EQUAL(TagLib::uint(2), dict["ARTIST"].size());
CPPUNIT_ASSERT_EQUAL(String("artøst 1"), dict["ARTIST"][0]);
CPPUNIT_ASSERT_EQUAL(String("artöst 2"), dict["ARTIST"][1]);
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC);

View File

@ -10,11 +10,13 @@
#include <uniquefileidentifierframe.h>
#include <textidentificationframe.h>
#include <attachedpictureframe.h>
#include <unsynchronizedlyricsframe.h>
#include <generalencapsulatedobjectframe.h>
#include <relativevolumeframe.h>
#include <popularimeterframe.h>
#include <urllinkframe.h>
#include <tdebug.h>
#include <tpropertymap.h>
#include "utils.h"
using namespace std;
@ -67,6 +69,8 @@ class TestID3v2 : public CppUnit::TestFixture
CPPUNIT_TEST(testDowngradeTo23);
// CPPUNIT_TEST(testUpdateFullDate22); TODO TYE+TDA should be upgraded to TDRC together
CPPUNIT_TEST(testCompressedFrameWithBrokenLength);
CPPUNIT_TEST(testPropertyInterface);
CPPUNIT_TEST(testPropertyInterface2);
CPPUNIT_TEST_SUITE_END();
public:
@ -547,6 +551,83 @@ public:
CPPUNIT_ASSERT_EQUAL(TagLib::uint(86414), frame->picture().size());
}
void testPropertyInterface()
{
ScopedFileCopy copy("rare_frames", ".mp3");
string newname = copy.fileName();
MPEG::File f(newname.c_str());
PropertyMap dict = f.ID3v2Tag(false)->properties();
CPPUNIT_ASSERT_EQUAL(uint(6), dict.size());
CPPUNIT_ASSERT(dict.contains("USERTEXTDESCRIPTION1"));
CPPUNIT_ASSERT(dict.contains("QuodLibet::USERTEXTDESCRIPTION2"));
CPPUNIT_ASSERT_EQUAL(uint(2), dict["USERTEXTDESCRIPTION1"].size());
CPPUNIT_ASSERT_EQUAL(uint(2), dict["QuodLibet::USERTEXTDESCRIPTION2"].size());
CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["USERTEXTDESCRIPTION1"][0]);
CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["USERTEXTDESCRIPTION1"][1]);
CPPUNIT_ASSERT_EQUAL(String("userTextData1"), dict["QuodLibet::USERTEXTDESCRIPTION2"][0]);
CPPUNIT_ASSERT_EQUAL(String("userTextData2"), dict["QuodLibet::USERTEXTDESCRIPTION2"][1]);
CPPUNIT_ASSERT_EQUAL(String("Pop"), dict["GENRE"].front());
CPPUNIT_ASSERT_EQUAL(String("http://a.user.url"), dict["URL:USERURL"].front());
CPPUNIT_ASSERT_EQUAL(String("http://a.user.url/with/empty/description"), dict["URL"].front());
CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front());
CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size());
CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front());
}
void testPropertyInterface2()
{
ID3v2::Tag tag;
ID3v2::UnsynchronizedLyricsFrame *frame1 = new ID3v2::UnsynchronizedLyricsFrame();
frame1->setDescription("test");
frame1->setText("la-la-la test");
tag.addFrame(frame1);
ID3v2::UnsynchronizedLyricsFrame *frame2 = new ID3v2::UnsynchronizedLyricsFrame();
frame2->setDescription("");
frame2->setText("la-la-la nodescription");
tag.addFrame(frame2);
ID3v2::AttachedPictureFrame *frame3 = new ID3v2::AttachedPictureFrame();
frame3->setDescription("test picture");
tag.addFrame(frame3);
ID3v2::TextIdentificationFrame *frame4 = new ID3v2::TextIdentificationFrame("TIPL");
frame4->setText("single value is invalid for TIPL");
tag.addFrame(frame4);
ID3v2::TextIdentificationFrame *frame5 = new ID3v2::TextIdentificationFrame("TMCL");
StringList tmclData;
tmclData.append("VIOLIN");
tmclData.append("a violinist");
tmclData.append("PIANO");
tmclData.append("a pianist");
frame5->setText(tmclData);
tag.addFrame(frame5);
PropertyMap properties = tag.properties();
CPPUNIT_ASSERT_EQUAL(2u, properties.unsupportedData().size());
CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL"));
CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC"));
CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN"));
CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO"));
CPPUNIT_ASSERT_EQUAL(String("a violinist"), properties["PERFORMER:VIOLIN"].front());
CPPUNIT_ASSERT_EQUAL(String("a pianist"), properties["PERFORMER:PIANO"].front());
CPPUNIT_ASSERT(properties.contains("LYRICS"));
CPPUNIT_ASSERT(properties.contains("LYRICS:TEST"));
tag.removeUnsupportedProperties(properties.unsupportedData());
CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty());
CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty());
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2);

View File

@ -21,6 +21,7 @@
#include <cppunit/extensions/HelperMacros.h>
#include <modfile.h>
#include <tpropertymap.h>
#include "utils.h"
using namespace std;
@ -51,6 +52,7 @@ class TestMod : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE(TestMod);
CPPUNIT_TEST(testReadTags);
CPPUNIT_TEST(testWriteTags);
CPPUNIT_TEST(testPropertyInterface);
CPPUNIT_TEST_SUITE_END();
public:
@ -75,6 +77,22 @@ public:
TEST_FILE_PATH_C("changed.mod")));
}
void testPropertyInterface()
{
Mod::Tag t;
PropertyMap properties;
properties["BLA"] = String("bla");
properties["ARTIST"] = String("artist1");
properties["ARTIST"].append("artist2");
properties["TITLE"] = String("title");
PropertyMap unsupported = t.setProperties(properties);
CPPUNIT_ASSERT(unsupported.contains("BLA"));
CPPUNIT_ASSERT(unsupported.contains("ARTIST"));
CPPUNIT_ASSERT_EQUAL(properties["ARTIST"], unsupported["ARTIST"]);
CPPUNIT_ASSERT(!unsupported.contains("TITLE"));
}
private:
void testRead(FileName fileName, const String &title, const String &comment)
{

View File

@ -4,6 +4,7 @@
#include <tag.h>
#include <tstringlist.h>
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <oggfile.h>
#include <vorbisfile.h>
#include <oggpageheader.h>
@ -17,6 +18,8 @@ class TestOGG : public CppUnit::TestFixture
CPPUNIT_TEST_SUITE(TestOGG);
CPPUNIT_TEST(testSimple);
CPPUNIT_TEST(testSplitPackets);
CPPUNIT_TEST(testDictInterface1);
CPPUNIT_TEST(testDictInterface2);
CPPUNIT_TEST_SUITE_END();
public:
@ -51,6 +54,51 @@ public:
delete f;
}
void testDictInterface1()
{
ScopedFileCopy copy("empty", ".ogg");
string newname = copy.fileName();
Vorbis::File *f = new Vorbis::File(newname.c_str());
CPPUNIT_ASSERT_EQUAL(uint(0), f->tag()->properties().size());
PropertyMap newTags;
StringList values("value 1");
values.append("value 2");
newTags["ARTIST"] = values;
f->tag()->setProperties(newTags);
PropertyMap map = f->tag()->properties();
CPPUNIT_ASSERT_EQUAL(uint(1), map.size());
CPPUNIT_ASSERT_EQUAL(uint(2), map["ARTIST"].size());
CPPUNIT_ASSERT_EQUAL(String("value 1"), map["ARTIST"][0]);
delete f;
}
void testDictInterface2()
{
ScopedFileCopy copy("test", ".ogg");
string newname = copy.fileName();
Vorbis::File *f = new Vorbis::File(newname.c_str());
PropertyMap tags = f->tag()->properties();
CPPUNIT_ASSERT_EQUAL(uint(2), tags["UNUSUALTAG"].size());
CPPUNIT_ASSERT_EQUAL(String("usual value"), tags["UNUSUALTAG"][0]);
CPPUNIT_ASSERT_EQUAL(String("another value"), tags["UNUSUALTAG"][1]);
CPPUNIT_ASSERT_EQUAL(String(L"öäüoΣø"), tags["UNICODETAG"][0]);
tags["UNICODETAG"][0] = L"νεω ναλυε";
tags.erase("UNUSUALTAG");
f->tag()->setProperties(tags);
CPPUNIT_ASSERT_EQUAL(String(L"νεω ναλυε"), f->tag()->properties()["UNICODETAG"][0]);
CPPUNIT_ASSERT_EQUAL(false, f->tag()->properties().contains("UNUSUALTAG"));
delete f;
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestOGG);