Implement the PropertyMap interface for MP4

This commit is contained in:
Lukáš Lalinský 2012-11-22 10:40:22 +01:00
parent 1b813d9d6c
commit 353eb9f00f
9 changed files with 250 additions and 4 deletions

View File

@ -132,6 +132,7 @@ namespace TagLib {
* has no tag at all, APE will be created.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the APE::Properties for this file. If no audio properties
* were read then this will return a null pointer.

View File

@ -29,6 +29,7 @@
#include <tdebug.h>
#include <tstring.h>
#include <tpropertymap.h>
#include "mp4atom.h"
#include "mp4tag.h"
#include "mp4file.h"
@ -90,6 +91,21 @@ MP4::File::tag() const
return d->tag;
}
PropertyMap MP4::File::properties() const
{
return d->tag->properties();
}
void MP4::File::removeUnsupportedProperties(const StringList &properties)
{
d->tag->removeUnsupportedProperties(properties);
}
PropertyMap MP4::File::setProperties(const PropertyMap &properties)
{
return d->tag->setProperties(properties);
}
MP4::Properties *
MP4::File::audioProperties() const
{

View File

@ -88,6 +88,22 @@ namespace TagLib {
*/
Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
*/
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.
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the MP4 audio properties for this file.
*/

View File

@ -29,6 +29,7 @@
#include <tdebug.h>
#include <tstring.h>
#include <tpropertymap.h>
#include "mp4atom.h"
#include "mp4tag.h"
#include "id3v1genres.h"
@ -759,3 +760,153 @@ MP4::Tag::itemListMap()
return d->items;
}
static const char *keyTranslation[][2] = {
{ "\251nam", "TITLE" },
{ "\251ART", "ARTIST" },
{ "\251alb", "ALBUM" },
{ "\251cmt", "COMMENT" },
{ "\251gen", "GENRE" },
{ "\251day", "DATE" },
{ "\251wrt", "COMPOSER" },
{ "\251grp", "GROUPING" },
{ "trkn", "TRACKNUMBER" },
{ "disk", "DISCNUMBER" },
{ "cpil", "COMPILATION" },
{ "tmpo", "BPM" },
{ "cprt", "COPYRIGHT" },
{ "\251lyr", "LYRICS" },
{ "\251too", "ENCODEDBY" },
{ "soal", "ALBUMSORT" },
{ "soaa", "ALBUMARTISTSORT" },
{ "soar", "ARTISTSORT" },
{ "sonm", "TITLESORT" },
{ "soco", "COMPOSERSORT" },
{ "sosn", "SHOWSORT" },
{ "----:com.apple.iTunes:MusicBrainz Track Id", "MUSICBRAINZ_TRACKID" },
{ "----:com.apple.iTunes:MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" },
{ "----:com.apple.iTunes:MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" },
{ "----:com.apple.iTunes:MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" },
{ "----:com.apple.iTunes:MusicBrainz Work Id", "MUSICBRAINZ_WORKID" },
{ "----:com.apple.iTunes:ASIN", "ASIN" },
{ "----:com.apple.iTunes:LABEL", "LABEL" },
{ "----:com.apple.iTunes:LYRICIST", "LYRICIST" },
{ "----:com.apple.iTunes:CONDUCTOR", "CONDUCTOR" },
{ "----:com.apple.iTunes:REMIXER", "REMIXER" },
{ "----:com.apple.iTunes:ENGINEER", "ENGINEER" },
{ "----:com.apple.iTunes:PRODUCER", "PRODUCER" },
{ "----:com.apple.iTunes:DJMIXER", "DJMIXER" },
{ "----:com.apple.iTunes:MIXER", "MIXER" },
{ "----:com.apple.iTunes:SUBTITLE", "SUBTITLE" },
{ "----:com.apple.iTunes:DISCSUBTITLE", "DISCSUBTITLE" },
{ "----:com.apple.iTunes:MOOD", "MOOD" },
{ "----:com.apple.iTunes:ISRC", "ISRC" },
{ "----:com.apple.iTunes:CATALOGNUMBER", "CATALOGNUMBER" },
{ "----:com.apple.iTunes:BARCODE", "BARCODE" },
{ "----:com.apple.iTunes:SCRIPT", "SCRIPT" },
{ "----:com.apple.iTunes:LANGUAGE", "LANGUAGE" },
{ "----:com.apple.iTunes:LICENSE", "LICENSE" },
{ "----:com.apple.iTunes:MEDIA", "MEDIA" },
};
PropertyMap MP4::Tag::properties() const
{
static Map<String, String> keyMap;
if(keyMap.isEmpty()) {
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
for(int i = 0; i < numKeys; i++) {
keyMap[keyTranslation[i][0]] = keyTranslation[i][1];
}
}
PropertyMap props;
MP4::ItemListMap::ConstIterator it = d->items.begin();
for(; it != d->items.end(); ++it) {
if(keyMap.contains(it->first)) {
String key = keyMap[it->first];
if(key == "TRACKNUMBER" || key == "DISCNUMBER") {
MP4::Item::IntPair ip = it->second.toIntPair();
String value = String::number(ip.first);
if(ip.second) {
value += "/" + String::number(ip.second);
}
props[key] = value;
}
else if(key == "BPM") {
props[key] = String::number(it->second.toInt());
}
else if(key == "COMPILATION") {
props[key] = String::number(it->second.toBool());
}
else {
props[key] = it->second.toStringList();
}
}
else {
props.unsupportedData().append(it->first);
}
}
return props;
}
void MP4::Tag::removeUnsupportedProperties(const StringList &props)
{
StringList::ConstIterator it = props.begin();
for(; it != props.end(); ++it)
d->items.erase(*it);
}
PropertyMap MP4::Tag::setProperties(const PropertyMap &props)
{
static Map<String, String> reverseKeyMap;
if(reverseKeyMap.isEmpty()) {
int numKeys = sizeof(keyTranslation) / sizeof(keyTranslation[0]);
for(int i = 0; i < numKeys; i++) {
reverseKeyMap[keyTranslation[i][1]] = keyTranslation[i][0];
}
}
PropertyMap origProps = properties();
PropertyMap::ConstIterator it = origProps.begin();
for(; it != origProps.end(); ++it) {
if(!props.contains(it->first) || props[it->first].isEmpty()) {
d->items.erase(reverseKeyMap[it->first]);
}
}
PropertyMap ignoredProps;
it = props.begin();
for(; it != props.end(); ++it) {
if(reverseKeyMap.contains(it->first)) {
String name = reverseKeyMap[it->first];
if(it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") {
int first = 0, second = 0;
StringList parts = StringList::split(it->second.front(), "/");
if(parts.size() > 0) {
first = parts[0].toInt();
if(parts.size() > 1) {
second = parts[1].toInt();
}
d->items[name] = MP4::Item(first, second);
}
}
else if(it->first == "BPM") {
int value = it->second.front().toInt();
d->items[name] = MP4::Item(value);
}
else if(it->first == "COMPILATION") {
bool value = it->second.front().toInt();
d->items[name] = MP4::Item(value > 0);
}
else {
d->items[name] = it->second;
}
}
else {
ignoredProps.insert(it->first, it->second);
}
}
return ignoredProps;
}

View File

@ -67,6 +67,10 @@ namespace TagLib {
ItemListMap &itemListMap();
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList& properties);
PropertyMap setProperties(const PropertyMap &properties);
private:
AtomDataList parseData2(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);
TagLib::ByteVectorList parseData(Atom *atom, TagLib::File *file, int expectedFlags = -1, bool freeForm = false);

View File

@ -355,7 +355,7 @@ static const char *frameTranslation[][2] = {
{ "TLAN", "LANGUAGE" },
{ "TLEN", "LENGTH" },
//{ "TMCL", "MUSICIANCREDITS" }, handled separately
{ "TMED", "MEDIATYPE" },
{ "TMED", "MEDIA" },
{ "TMOO", "MOOD" },
{ "TOAL", "ORIGINALALBUM" },
{ "TOFN", "ORIGINALFILENAME" },

View File

@ -69,6 +69,7 @@
#include "s3mfile.h"
#include "itfile.h"
#include "xmfile.h"
#include "mp4file.h"
using namespace TagLib;
@ -152,12 +153,11 @@ PropertyMap File::properties() const
return dynamic_cast<const WavPack::File* >(this)->properties();
if(dynamic_cast<const XM::File* >(this))
return dynamic_cast<const XM::File* >(this)->properties();
if(dynamic_cast<const MP4::File* >(this))
return dynamic_cast<const MP4::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();
}
@ -193,6 +193,8 @@ void File::removeUnsupportedProperties(const StringList &properties)
dynamic_cast<WavPack::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<XM::File* >(this))
dynamic_cast<XM::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<MP4::File* >(this))
dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties);
else
tag()->removeUnsupportedProperties(properties);
}
@ -231,6 +233,8 @@ PropertyMap File::setProperties(const PropertyMap &properties)
return dynamic_cast<WavPack::File* >(this)->setProperties(properties);
else if(dynamic_cast<XM::File* >(this))
return dynamic_cast<XM::File* >(this)->setProperties(properties);
else if(dynamic_cast<MP4::File* >(this))
return dynamic_cast<MP4::File* >(this)->setProperties(properties);
else
return tag()->setProperties(properties);
}

View File

@ -82,6 +82,9 @@ namespace TagLib {
* - ENCODEDBY
* - MOOD
* - COMMENT
* - MEDIA
* - CATALOGNUMBER
* - BARCODE
*
* MusicBrainz identifiers:
*

View File

@ -3,6 +3,7 @@
#include <tag.h>
#include <mp4tag.h>
#include <tbytevectorlist.h>
#include <tpropertymap.h>
#include <mp4atom.h>
#include <mp4file.h>
#include <cppunit/extensions/HelperMacros.h>
@ -25,6 +26,7 @@ class TestMP4 : public CppUnit::TestFixture
CPPUNIT_TEST(testCovrRead);
CPPUNIT_TEST(testCovrWrite);
CPPUNIT_TEST(testCovrRead2);
CPPUNIT_TEST(testProperties);
CPPUNIT_TEST_SUITE_END();
public:
@ -224,6 +226,55 @@ public:
delete f;
}
void testProperties()
{
MP4::File f(TEST_FILE_PATH_C("has-tags.m4a"));
PropertyMap tags = f.properties();
CPPUNIT_ASSERT_EQUAL(StringList("Test Artist"), tags["ARTIST"]);
tags["TRACKNUMBER"] = StringList("2/4");
tags["DISCNUMBER"] = StringList("3/5");
tags["BPM"] = StringList("123");
tags["ARTIST"] = StringList("Foo Bar");
tags["COMPILATION"] = StringList("1");
f.setProperties(tags);
tags = f.properties();
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("trkn"));
CPPUNIT_ASSERT_EQUAL(2, f.tag()->itemListMap()["trkn"].toIntPair().first);
CPPUNIT_ASSERT_EQUAL(4, f.tag()->itemListMap()["trkn"].toIntPair().second);
CPPUNIT_ASSERT_EQUAL(StringList("2/4"), tags["TRACKNUMBER"]);
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("disk"));
CPPUNIT_ASSERT_EQUAL(3, f.tag()->itemListMap()["disk"].toIntPair().first);
CPPUNIT_ASSERT_EQUAL(5, f.tag()->itemListMap()["disk"].toIntPair().second);
CPPUNIT_ASSERT_EQUAL(StringList("3/5"), tags["DISCNUMBER"]);
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("tmpo"));
CPPUNIT_ASSERT_EQUAL(123, f.tag()->itemListMap()["tmpo"].toInt());
CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]);
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("\251ART"));
CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), f.tag()->itemListMap()["\251ART"].toStringList());
CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]);
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil"));
CPPUNIT_ASSERT_EQUAL(true, f.tag()->itemListMap()["cpil"].toBool());
CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["COMPILATION"]);
tags["COMPILATION"] = StringList("0");
f.setProperties(tags);
tags = f.properties();
CPPUNIT_ASSERT(f.tag()->itemListMap().contains("cpil"));
CPPUNIT_ASSERT_EQUAL(false, f.tag()->itemListMap()["cpil"].toBool());
CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["COMPILATION"]);
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);