mirror of
https://github.com/taglib/taglib.git
synced 2025-06-04 01:28:21 -04:00
Merge branch 'master' of https://github.com/supermihi/taglib
This commit is contained in:
commit
76222cb1eb
@ -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
|
||||
)
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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 &);
|
||||
|
@ -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()) {
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 &);
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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 &);
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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".
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
28
taglib/tag.h
28
taglib/tag.h
@ -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.
|
||||
|
@ -24,6 +24,7 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "tagunion.h"
|
||||
#include "tstringlist.h"
|
||||
|
||||
using namespace TagLib;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
202
taglib/toolkit/tpropertymap.cpp
Normal file
202
taglib/toolkit/tpropertymap.cpp
Normal 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();
|
||||
}
|
184
taglib/toolkit/tpropertymap.h
Normal file
184
taglib/toolkit/tpropertymap.h
Normal 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_ */
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
BIN
tests/data/rare_frames.mp3
Normal file
Binary file not shown.
BIN
tests/data/test.ogg
Normal file
BIN
tests/data/test.ogg
Normal file
Binary file not shown.
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user