mpeg/xingheader.h
mpeg/id3v1/id3v1tag.h
mpeg/id3v1/id3v1genres.h
- mpeg/id3v2/id3v2dicttools.h
mpeg/id3v2/id3v2extendedheader.h
mpeg/id3v2/id3v2frame.h
mpeg/id3v2/id3v2header.h
)
set(id3v2_SRCS
- mpeg/id3v2/id3v2dicttools.cpp
mpeg/id3v2/id3v2framefactory.cpp
mpeg/id3v2/id3v2synchdata.cpp
mpeg/id3v2/id3v2tag.cpp
d->textEncoding = encoding;
}
+PropertyMap CommentsFrame::asDescription() const
+{
+ String key = PropertyMap::prepareKey(description());
+ PropertyMap map;
+ if(key.isEmpty())
+ key = "COMMENT";
+ if(key.isNull())
+ map.unsupportedData().append(L"COMM/" + description());
+ else
+ map.insert(key, text());
+ return map;
+}
+
CommentsFrame *CommentsFrame::findByDescription(const ID3v2::Tag *tag, const String &d) // static
{
ID3v2::FrameList comments = tag->frameList("COMM");
*/
void setTextEncoding(String::Type encoding);
+ /*!
+ * Parses this frame as PropertyMap.
+ * - description() will be used as key
+ * - if description() is empty, the key will be "COMMENT"
+ * - if description() is not a valid PropertyMap key, the frame will be
+ * marked unsupported by an entry "COMM/<description>" in the unsupportedData()
+ * attribute of the returned map.
+ * - The single value will be the frame's text().
+ */
+ PropertyMap asDescription() const;
+
/*!
* Comments each have a unique description. This searches for a comment
* frame with the decription \a d and returns a pointer to it. If no
d->textEncoding = encoding;
}
+PropertyMap TextIdentificationFrame::asProperties() const
+{
+ if(frameID() == "TIPL")
+ return makeTIPLProperties();
+ if(frameID() == "TMCL")
+ return makeTMCLProperties();
+ PropertyMap map;
+ String tagName = frameIDToTagName(frameID());
+ if(tagName.isNull()) {
+ map.unsupportedData().append(frameID());
+ return map;
+ }
+ StringList values = fieldList();
+ if(tagName == "GENRE") {
+ // Special case: Support ID3v1-style genre numbers. They are not officially supported in
+ // ID3v2, however it seems that still a lot of programs use them.
+ for(StringList::Iterator it = values.begin(); it != values.end(); ++it) {
+ bool ok = false;
+ int test = it->toInt(&ok); // test if the genre value is an integer
+ if(ok)
+ *it = ID3v1::genre(test);
+ }
+ } else if(tagName == "DATE") {
+ for (StringList::Iterator it = values.begin(); it != values.end(); ++it) {
+ // ID3v2 specifies ISO8601 timestamps which contain a 'T' as separator between date and time.
+ // Since this is unusual in other formats, the T is removed.
+ int tpos = it->find("T");
+ if (tpos != -1)
+ (*it)[tpos] = ' ';
+ }
+ }
+ return KeyValuePair(tagName, values);
+}
+
////////////////////////////////////////////////////////////////////////////////
// TextIdentificationFrame protected members
////////////////////////////////////////////////////////////////////////////////
parseFields(fieldData(data));
}
+// array of allowed TIPL prefixes and their corresponding key value
+static const uint involvedPeopleSize = 5;
+static const char* involvedPeople[2] = {
+ {"ARRANGER", "ARRANGER"},
+ {"ENGINEER", "ENGINEER"},
+ {"PRODUCER", "PRODUCER"},
+ {"DJ-MIX", "DJMIXER"},
+ {"MIX", "MIXER"}
+};
+
+PropertyMap TextIdentificationFrame::makeTIPLProperties() const
+{
+ PropertyMap map;
+ if(fieldList().size() % 2 != 0){
+ // according to the ID3 spec, TIPL must contain an even number of entries
+ map.unsupportedData().append(frameID());
+ return map;
+ }
+ for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) {
+ bool found = false;
+ for(uint i = 0; i < involvedPeopleSize; ++i)
+ if(*it == involvedPeople[i][0]) {
+ map.insert(involvedPeople[i][1], (++it).split(","));
+ found = true;
+ break;
+ }
+ if(!found){
+ // invalid involved role -> mark whole frame as unsupported in order to be consisten with writing
+ map.clear();
+ map.unsupportedData().append(frameID());
+ return map;
+ }
+ }
+ return map;
+}
+
+PropertyMap TextIdentificationFrame::makeTMCLProperties() const
+{
+ PropertyMap map;
+ if(fieldList().size() % 2 != 0){
+ // according to the ID3 spec, TMCL must contain an even number of entries
+ map.unsupportedData().append(frameID());
+ return map;
+ }
+ for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it) {
+ String key = PropertyMap::prepareKey(*it);
+ if(key.isNull()) {
+ // instrument is not a valid key -> frame unsupported
+ map.clear();
+ map.unsupportedData().append(frameID());
+ return map;
+ }
+ map.insert(key, (++it).split(","));
+ }
+ return map;
+}
+
////////////////////////////////////////////////////////////////////////////////
// UserTextIdentificationFrame public members
////////////////////////////////////////////////////////////////////////////////
PropertyMap UserTextIdentificationFrame::asProperties() const
{
String tagName = description();
- StringList l(fieldList());
- // this is done because taglib stores the description also as first entry
- // in the field list. (why?)
- StringList::Iterator tagIt = l.find(tagName);
- if(tagIt != l.end())
- l.erase(tagIt);
- // Quodlibet/Exfalso use QuodLibet::<tagname> if you set an arbitrary ID3
- // tag.
+ // Quodlibet/Exfalso use QuodLibet::<tagname> if you set an arbitrary ID3 tag.
int pos = tagName.find("::");
tagName = (pos != -1) ? tagName.substr(pos+2).upper() : tagName.upper();
PropertyMap map;
String key = map.prepareKey(tagName);
if(key.isNull()) // this frame's description is not a valid PropertyMap key -> add to unsupported list
- map.unsupportedData().append("TXXX/" + description());
+ map.unsupportedData().append(L"TXXX/" + description());
else
- map.insert(key, l);
+ for(StringList::ConstIterator it = fieldList().begin(); it != fieldList().end(); ++it)
+ if(*it != description())
+ map.insert(key, *it);
return map;
}
*/
StringList fieldList() const;
+ PropertyMap asProperties() const;
+
protected:
// Reimplementations.
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;
};
void setText(const StringList &fields);
/*!
- * Reimplement function.
+ * A UserTextIdentificationFrame is parsed into a PropertyMap as follows:
+ * - the key is the frame's description, uppercased
+ * - if the description contains '::', only the substring after that
+ * separator is considered as key (compatibility with exfalso)
+ * - if the above rules don't yield a valid key (e.g. containing non-ASCII
+ * characters), the returned map will contain an entry "TXXX/<description>"
+ * in its unsupportedData() list.
+ * - The values will be copies of the fieldList().
+ * - If the description() appears as value in fieldList(), it will be omitted
+ * in the value list, in order to be compatible with TagLib which copies
+ * the description() into the fieldList().
*/
PropertyMap asProperties() const;
d->textEncoding = encoding;
}
+PropertyMap UnsynchronizedLyricsFrame::asProperties() const
+{
+ PropertyMap map;
+ map.insert("LYRICS", text());
+ return map;
+}
+
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////
*/
void setTextEncoding(String::Type encoding);
+
+ /*!
+ * Parses this frame as PropertyMap. The returned map will contain a single key
+ * "LYRICS" with the text() as single value.
+ */
+ PropertyMap asProperties() const;
+
protected:
// Reimplementations.
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);
d->description = s;
}
+PropertyMap UserUrlLinkFrame::asProperties() const
+{
+ String key = PropertyMap::prepareKey(description());
+ PropertyMap map;
+ if(key.isEmpty())
+ key = "URL";
+ if(key.isNull())
+ map.unsupportedData().append(L"WXXX/" + description());
+ else
+ map.insert(key, url());
+ return map;
+}
+
void UserUrlLinkFrame::parseFields(const ByteVector &data)
{
if(data.size() < 2) {
virtual void setText(const String &s);
virtual String toString() const;
+ PropertyMap asProperties() const;
protected:
virtual void parseFields(const ByteVector &data);
*/
void setDescription(const String &s);
+ /*!
+ * Parses the UserUrlLinkFrame as PropertyMap. The description() is taken as key,
+ * and the URL as single value.
+ * - if description() is empty, the key will be "URL".
+ * - otherwise, if description() is not a valid key (e.g. containing non-ASCII
+ * characters), the returned map will contain an entry "WXXX/<description>"
+ * in its unsupportedData() list.
+ */
+ PropertyMap asProperties() const;
+
protected:
virtual void parseFields(const ByteVector &data);
virtual ByteVector renderFields() const;
#include "id3v2frame.h"
#include "id3v2synchdata.h"
+#include "tpropertymap.h"
using namespace TagLib;
using namespace ID3v2;
return checkEncoding(fields, encoding, header()->version());
}
-static const uint frameTranslationSize = 55;
+static const uint frameTranslationSize = 53;
static const char *frameTranslation[][2] = {
// Text information frames
{ "TALB", "ALBUM"},
{ "TENC", "ENCODEDBY" },
{ "TEXT", "LYRICIST" },
{ "TFLT", "FILETYPE" },
- { "TIPL", "INVOLVEDPEOPLE" },
+ //{ "TIPL", "INVOLVEDPEOPLE" }, handled separately
{ "TIT1", "CONTENTGROUP" },
{ "TIT2", "TITLE"},
{ "TIT3", "SUBTITLE" },
{ "TKEY", "INITIALKEY" },
{ "TLAN", "LANGUAGE" },
{ "TLEN", "LENGTH" },
- { "TMCL", "MUSICIANCREDITS" },
+ //{ "TMCL", "MUSICIANCREDITS" }, handled separately
{ "TMED", "MEDIATYPE" },
{ "TMOO", "MOOD" },
{ "TOAL", "ORIGINALALBUM" },
namespace TagLib {
class StringList;
+ class PropertyMap;
namespace ID3v2 {
#include "id3v2synchdata.h"
#include "tbytevector.h"
#include "id3v1genres.h"
+#include "tpropertymap.h"
+
+#include "frames/textidentificationframe.h"
+#include "frames/commentsframe.h"
+#include "frames/urllinkframe.h"
+#include "frames/uniquefileidentifierframe.h"
+#include "frames/unsynchronizedlyricsframe.h"
+#include "frames/unknownframe.h"
using namespace TagLib;
using namespace ID3v2;
PropertyMap ID3v2::Tag::properties() const
{
PropertyMap properties;
- for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it) {
- ByteVector id = (*it)->frameID();
- if (ignored(id)) {
- debug("toDict() found ignored id3 frame: " + id);
- } else if (deprecated(id)) {
- debug("toDict() found deprecated id3 frame: " + id);
- } else {
- // in the future, something like dict[frame->tagName()].append(frame->values())
- // might replace the following lines.
- KeyValuePair kvp = parseFrame(*it);
- dict[kvp.first].append(kvp.second);
- }
- }
- return dict;
+ for(FrameList::ConstIterator it = frameList().begin(); it != frameList().end(); ++it)
+ properties.merge((*it)->asProperties());
+ return properties;
}
-void ID3v2::Tag::fromDict(const TagDict &dict)
+PropertyMap ID3v2::Tag::setProperties(const PropertyMap &properties)
{
FrameList toRemove;
// first find out what frames to remove; we do not remove in-place
#include "mpegfile.h"
#include "mpegheader.h"
+#include "tpropertymap.h"
using namespace TagLib;
{
setUnion(Track, i);
}
-TagDict TagUnion::toDict() const
-{
- for (int i = 0; i < 3; ++i)
- if (d->tags[i])
- return d->tags[i]->toDict();
- TagDict dict;
- return dict;
-}
-
-void TagUnion::fromDict(const TagDict &dict)
-{
- for (int i = 0; i < 3; ++i)
- if (d->tags[i])
- d->tags[i]->fromDict(dict);
-}
bool TagUnion::isEmpty() const
{
virtual void setTrack(uint i);
virtual bool isEmpty() const;
- virtual TagDict toDict() const;
- virtual void fromDict(const TagDict &);
-
template <class T> T *access(int index, bool create)
{
if(!create || tag(index))
* This map implements a generic representation of textual audio metadata
* ("tags") realized as pairs of a case-insensitive key
* and a nonempty list of corresponding values, each value being an an arbitrary
- * Unicode String.
+ * unicode String.
* The key has the same restrictions as in the vorbis comment specification,
* i.e. it must contain at least one character; all printable ASCII characters
* except '=' and '~' are allowed.