From ade8dc1a218e43c3909c4eede58f4e28e09da73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Tue, 20 Nov 2012 14:15:16 +0100 Subject: [PATCH 01/10] Fix opening of read-only files on Windows The CreateFile* functions return INVALID_HANDLE_VALUE on error, not NULL. http://article.gmane.org/gmane.comp.kde.devel.taglib/2346 --- taglib/toolkit/tfilestream.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 16efb579..35300de6 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -56,10 +56,16 @@ namespace { { DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); + HANDLE handle; if(wcslen(path) > 0) - return CreateFileW(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + handle = CreateFileW(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); else - return CreateFileA(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + handle = CreateFileA(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + + if(handle == INVALID_HANDLE_VALUE) + handle = NULL; + + return handle; } size_t fread(void *ptr, size_t size, size_t nmemb, HANDLE stream) From 57b8ae6e1ca4822164d19865ef7ef1e8aee8b941 Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 20 Nov 2012 22:20:34 +0900 Subject: [PATCH 02/10] Fix reading read-only files in Win32 --- taglib/toolkit/tfilestream.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 16efb579..d68dbc82 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -52,6 +52,8 @@ namespace { // Using native file handles instead of file descriptors for reducing the resource consumption. + const HANDLE InvalidFile = INVALID_HANDLE_VALUE; + HANDLE openFile(const FileName &path, bool readOnly) { DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); @@ -82,6 +84,8 @@ namespace { // For non-Windows + const FILE *InvalidFile = 0; + struct FileNameHandle : public std::string { FileNameHandle(FileName name) : std::string(name) {} @@ -119,7 +123,7 @@ public: }; FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName, bool openReadOnly) : - file(0), + file(InvalidFile), name(fileName), readOnly(true), size(0) @@ -129,12 +133,12 @@ FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName, bool openRea if(!openReadOnly) file = openFile(name, false); - if(file) + if(file != InvalidFile) readOnly = false; else file = openFile(name, true); - if(!file) { + if(file == InvalidFile) { debug("Could not open file " + String((const char *) name)); } } From 3f6da779d2c68271417f7fe44c3c5ab7032692cf Mon Sep 17 00:00:00 2001 From: Tsuda Kageyu Date: Tue, 20 Nov 2012 22:46:03 +0900 Subject: [PATCH 03/10] Fix compilation in non-Win32 --- taglib/toolkit/tfilestream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index d68dbc82..52252023 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -84,7 +84,7 @@ namespace { // For non-Windows - const FILE *InvalidFile = 0; + FILE *const InvalidFile = 0; struct FileNameHandle : public std::string { From 45317ef7f203a7fac6719c553828ab4c2fa630dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Tue, 20 Nov 2012 18:34:51 +0100 Subject: [PATCH 04/10] Revert "Fix opening of read-only files on Windows" This reverts commit ade8dc1a218e43c3909c4eede58f4e28e09da73a. --- taglib/toolkit/tfilestream.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp index 6077d705..52252023 100644 --- a/taglib/toolkit/tfilestream.cpp +++ b/taglib/toolkit/tfilestream.cpp @@ -58,16 +58,10 @@ namespace { { DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); - HANDLE handle; if(wcslen(path) > 0) - handle = CreateFileW(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + return CreateFileW(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); else - handle = CreateFileA(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - - if(handle == INVALID_HANDLE_VALUE) - handle = NULL; - - return handle; + return CreateFileA(path, access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); } size_t fread(void *ptr, size_t size, size_t nmemb, HANDLE stream) From 15b601f053685ff592c1c59827c87d62034329e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 21 Nov 2012 14:40:26 +0100 Subject: [PATCH 05/10] Use PropertyMap in tagreader --- examples/tagreader.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/examples/tagreader.cpp b/examples/tagreader.cpp index 76fe0d1a..cc978eba 100644 --- a/examples/tagreader.cpp +++ b/examples/tagreader.cpp @@ -23,10 +23,12 @@ */ #include +#include #include #include #include +#include using namespace std; @@ -49,7 +51,7 @@ int main(int argc, char *argv[]) TagLib::Tag *tag = f.tag(); - cout << "-- TAG --" << endl; + cout << "-- TAG (basic) --" << endl; cout << "title - \"" << tag->title() << "\"" << endl; cout << "artist - \"" << tag->artist() << "\"" << endl; cout << "album - \"" << tag->album() << "\"" << endl; @@ -57,6 +59,23 @@ int main(int argc, char *argv[]) cout << "comment - \"" << tag->comment() << "\"" << endl; cout << "track - \"" << tag->track() << "\"" << endl; cout << "genre - \"" << tag->genre() << "\"" << endl; + + TagLib::PropertyMap tags = f.file()->properties(); + + unsigned int longest = 0; + for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) { + if (i->first.size() > longest) { + longest = i->first.size(); + } + } + + cout << "-- TAG (properties) --" << endl; + for(TagLib::PropertyMap::ConstIterator i = tags.begin(); i != tags.end(); ++i) { + for(TagLib::StringList::ConstIterator j = i->second.begin(); j != i->second.end(); ++j) { + cout << left << std::setw(longest) << i->first << " - " << '"' << *j << '"' << endl; + } + } + } if(!f.isNull() && f.audioProperties()) { From e75d6f616c82ebb00b405ce7dd03d77a7f589ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 21 Nov 2012 17:21:30 +0100 Subject: [PATCH 06/10] Add support for reading MusicBrainz IDs from ID3v2 tags to PropertyMap --- .../id3v2/frames/textidentificationframe.cpp | 16 ++---- .../frames/uniquefileidentifierframe.cpp | 30 +++++++++++ .../id3v2/frames/uniquefileidentifierframe.h | 10 ++++ taglib/mpeg/id3v2/id3v2frame.cpp | 53 ++++++++++++++++++- taglib/mpeg/id3v2/id3v2frame.h | 9 ++++ taglib/mpeg/id3v2/id3v2tag.cpp | 8 ++- taglib/toolkit/tpropertymap.h | 48 +++++++++++++++++ tests/test_id3v2.cpp | 24 ++++++++- 8 files changed, 182 insertions(+), 16 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp index 3287063c..70ea50f8 100644 --- a/taglib/mpeg/id3v2/frames/textidentificationframe.cpp +++ b/taglib/mpeg/id3v2/frames/textidentificationframe.cpp @@ -381,18 +381,12 @@ void UserTextIdentificationFrame::setDescription(const String &s) PropertyMap UserTextIdentificationFrame::asProperties() const { - String tagName = description(); - PropertyMap map; - String key = tagName.upper(); - 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); - } + String tagName = txxxToKey(description()); + StringList v = fieldList(); + for(StringList::ConstIterator it = v.begin(); it != v.end(); ++it) + if(it != v.begin()) + map.insert(tagName, *it); return map; } diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp index e12583ad..0725c729 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp @@ -24,8 +24,10 @@ ***************************************************************************/ #include +#include #include +#include "id3v2tag.h" #include "uniquefileidentifierframe.h" using namespace TagLib; @@ -87,6 +89,34 @@ String UniqueFileIdentifierFrame::toString() const return String::null; } +PropertyMap UniqueFileIdentifierFrame::asProperties() const +{ + PropertyMap map; + if(d->owner == "http://musicbrainz.org") { + map.insert("MUSICBRAINZ_RECORDINGID", String(d->identifier)); + } + else { + map.unsupportedData().append(frameID() + String("/") + d->owner); + } + return map; +} + +UniqueFileIdentifierFrame *UniqueFileIdentifierFrame::findByOwner(const ID3v2::Tag *tag, const String &o) // static +{ + ID3v2::FrameList comments = tag->frameList("UFID"); + + for(ID3v2::FrameList::ConstIterator it = comments.begin(); + it != comments.end(); + ++it) + { + UniqueFileIdentifierFrame *frame = dynamic_cast(*it); + if(frame && frame->owner() == o) + return frame; + } + + return 0; +} + void UniqueFileIdentifierFrame::parseFields(const ByteVector &data) { if(data.size() < 1) { diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h index 0cf4b8f6..add5a2e0 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.h @@ -94,6 +94,16 @@ namespace TagLib { virtual String toString() const; + PropertyMap asProperties() const; + + /*! + * UFID frames each have a unique owner. This searches for a UFID + * frame with the owner \a o and returns a pointer to it. + * + * \see owner() + */ + static UniqueFileIdentifierFrame *findByOwner(const Tag *tag, const String &o); + protected: virtual void parseFields(const ByteVector &data); virtual ByteVector renderFields() const; diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 1079785d..372778b1 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -44,6 +44,7 @@ #include "frames/urllinkframe.h" #include "frames/unsynchronizedlyricsframe.h" #include "frames/commentsframe.h" +#include "frames/uniquefileidentifierframe.h" #include "frames/unknownframe.h" using namespace TagLib; @@ -126,6 +127,10 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // return frame; } } + if(key == "MUSICBRAINZ_RECORDINGID" && values.size() == 1) { + UniqueFileIdentifierFrame *frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8)); + 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){ @@ -151,7 +156,7 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // 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); + return new UserTextIdentificationFrame(keyToTXXX(key), values, String::UTF8); } Frame::~Frame() @@ -387,6 +392,18 @@ static const char *frameTranslation[][2] = { //{ "USLT", "LYRICS" }, handled specially }; +static const TagLib::uint txxxFrameTranslationSize = 7; +static const char *txxxFrameTranslation[][2] = { + { "MusicBrainz Album Id", "MUSICBRAINZ_RELEASEID" }, + { "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "MusicBrainz Album Artist Id", "MUSICBRAINZ_RELEASEARTISTID" }, + { "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, + { "Acoustid Id", "ACOUSTID_ID" }, + { "Acoustid Fingerprint", "ACOUSTID_FINGERPRINT" }, + { "MusicIP PUID", "MUSICIP_PUID" }, +}; + Map &idMap() { static Map m; @@ -396,6 +413,18 @@ Map &idMap() return m; } +Map &txxxMap() +{ + static Map m; + if(m.isEmpty()) { + for(size_t i = 0; i < txxxFrameTranslationSize; ++i) { + String key = String(txxxFrameTranslation[i][0]).upper(); + m[key] = txxxFrameTranslation[i][1]; + } + } + return m; +} + // list of deprecated frames and their successors static const TagLib::uint deprecatedFramesSize = 4; static const char *deprecatedFrames[][2] = { @@ -435,6 +464,26 @@ ByteVector Frame::keyToFrameID(const String &s) return ByteVector::null; } +String Frame::txxxToKey(const String &description) +{ + Map &m = txxxMap(); + String d = description.upper(); + if(m.contains(d)) + return m[d]; + return d; +} + +String Frame::keyToTXXX(const String &s) +{ + static Map m; + if(m.isEmpty()) + for(size_t i = 0; i < txxxFrameTranslationSize; ++i) + m[txxxFrameTranslation[i][1]] = txxxFrameTranslation[i][0]; + if(m.contains(s.upper())) + return m[s]; + return s; +} + PropertyMap Frame::asProperties() const { if(dynamic_cast< const UnknownFrame *>(this)) { @@ -456,6 +505,8 @@ PropertyMap Frame::asProperties() const return dynamic_cast< const CommentsFrame* >(this)->asProperties(); else if(id == "USLT") return dynamic_cast< const UnsynchronizedLyricsFrame* >(this)->asProperties(); + else if(id == "UFID") + return dynamic_cast< const UniqueFileIdentifierFrame* >(this)->asProperties(); PropertyMap m; m.unsupportedData().append(id); return m; diff --git a/taglib/mpeg/id3v2/id3v2frame.h b/taglib/mpeg/id3v2/id3v2frame.h index 95c4070b..3e257d4f 100644 --- a/taglib/mpeg/id3v2/id3v2frame.h +++ b/taglib/mpeg/id3v2/id3v2frame.h @@ -274,6 +274,15 @@ namespace TagLib { */ static String frameIDToKey(const ByteVector &); + /*! + * Returns an appropriate TXXX frame description for the given free-form tag key. + */ + static String keyToTXXX(const String &); + + /*! + * Returns a free-form tag name for the given ID3 frame description. + */ + static String txxxToKey(const String &); /*! * This helper function splits the PropertyMap \a original into three ProperytMaps diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 54e63920..a1b169e1 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -379,10 +379,12 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) for(FrameList::ConstIterator fit = l.begin(); fit != l.end(); fit++) if (dynamic_cast(*fit) != 0) removeFrame(*fit); - } else if(it->size() == 4){ + } + else if(it->size() == 4){ ByteVector id = it->data(String::Latin1); removeFrames(id); - } else { + } + else { ByteVector id = it->substr(0,4).data(String::Latin1); if(it->size() <= 5) continue; // invalid specification @@ -396,6 +398,8 @@ void ID3v2::Tag::removeUnsupportedProperties(const StringList &properties) frame = CommentsFrame::findByDescription(this, description); else if(id == "USLT") frame = UnsynchronizedLyricsFrame::findByDescription(this, description); + else if(id == "UFID") + frame = UniqueFileIdentifierFrame::findByOwner(this, description); if(frame) removeFrame(frame); } diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 7f59b21e..8073003e 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -40,6 +40,54 @@ namespace TagLib { * Note that most metadata formats pose additional conditions on the tag keys. The * most popular ones (Vorbis, APE, ID3v2) should support all ASCII only words of * length between 2 and 16. + * + * This class can contain any tags, but here is a list of "well-known" tags that + * you might want to use: + * + * Basic tags: + * + * - TITLE + * - ALBUM + * - ARTIST + * - ALBUMARTIST + * - SUBTITLE + * - TRACKNUMBER + * - DISCNUMBER + * - DATE + * - ORIGINALDATE + * - GENRE + * - COMMENT + * + * Credits: + * + * - COMPOSER + * - LYRICIST + * - CONDUCTOR + * - REMIXER + * - PERFORMER: + * + * Other tags: + * + * - ISRC + * - ASIN + * - BPM + * - COPYRIGHT + * - ENCODEDBY + * - MOOD + * - COMMENT + * + * MusicBrainz identifiers: + * + * - MUSICBRAINZ_RECORDINGID + * - MUSICBRAINZ_RELEASEID + * - MUSICBRAINZ_RELEASEGROUPID + * - MUSICBRAINZ_WORKID + * - MUSICBRAINZ_ARTISTID + * - MUSICBRAINZ_RELEASEARTISTID + * - ACOUSTID_ID + * - ACOUSTID_FINGERPRINT + * - MUSICIP_PUID + * */ class TAGLIB_EXPORT PropertyMap: public SimplePropertyMap diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index 99f77101..b48bd68f 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -624,7 +624,7 @@ public: CPPUNIT_ASSERT_EQUAL(String("A COMMENT"), dict["COMMENT"].front()); CPPUNIT_ASSERT_EQUAL(1u, dict.unsupportedData().size()); - CPPUNIT_ASSERT_EQUAL(String("UFID"), dict.unsupportedData().front()); + CPPUNIT_ASSERT_EQUAL(String("UFID/supermihi@web.de"), dict.unsupportedData().front()); } void testPropertyInterface2() @@ -657,11 +657,23 @@ public: frame5->setText(tmclData); tag.addFrame(frame5); + ID3v2::UniqueFileIdentifierFrame *frame6 = new ID3v2::UniqueFileIdentifierFrame("http://musicbrainz.org", "152454b9-19ba-49f3-9fc9-8fc26545cf41"); + tag.addFrame(frame6); + + ID3v2::UniqueFileIdentifierFrame *frame7 = new ID3v2::UniqueFileIdentifierFrame("http://example.com", "123"); + tag.addFrame(frame7); + + ID3v2::UserTextIdentificationFrame *frame8 = new ID3v2::UserTextIdentificationFrame(); + frame8->setDescription("MusicBrainz Album Id"); + frame8->setText("95c454a5-d7e0-4d8f-9900-db04aca98ab3"); + tag.addFrame(frame8); + PropertyMap properties = tag.properties(); - CPPUNIT_ASSERT_EQUAL(2u, properties.unsupportedData().size()); + CPPUNIT_ASSERT_EQUAL(3u, properties.unsupportedData().size()); CPPUNIT_ASSERT(properties.unsupportedData().contains("TIPL")); CPPUNIT_ASSERT(properties.unsupportedData().contains("APIC")); + CPPUNIT_ASSERT(properties.unsupportedData().contains("UFID/http://example.com")); CPPUNIT_ASSERT(properties.contains("PERFORMER:VIOLIN")); CPPUNIT_ASSERT(properties.contains("PERFORMER:PIANO")); @@ -671,9 +683,17 @@ public: CPPUNIT_ASSERT(properties.contains("LYRICS")); CPPUNIT_ASSERT(properties.contains("LYRICS:TEST")); + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RECORDINGID")); + CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_RECORDINGID"].front()); + + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RELEASEID")); + CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_RELEASEID"].front()); + tag.removeUnsupportedProperties(properties.unsupportedData()); CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty()); CPPUNIT_ASSERT(tag.frameList("TIPL").isEmpty()); + CPPUNIT_ASSERT_EQUAL((ID3v2::UniqueFileIdentifierFrame *)0, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://example.com")); + CPPUNIT_ASSERT_EQUAL(frame6, ID3v2::UniqueFileIdentifierFrame::findByOwner(&tag, "http://musicbrainz.org")); } void testDeleteFrame() From c5dade5ee7073ee2d282af3a1fe70b598cb2eba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 21 Nov 2012 17:24:32 +0100 Subject: [PATCH 07/10] Use names that are consistent with Vorbis Comments --- taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp | 2 +- taglib/mpeg/id3v2/id3v2frame.cpp | 6 +++--- taglib/toolkit/tpropertymap.h | 6 +++--- tests/test_id3v2.cpp | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp index 0725c729..a0e842e0 100644 --- a/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp +++ b/taglib/mpeg/id3v2/frames/uniquefileidentifierframe.cpp @@ -93,7 +93,7 @@ PropertyMap UniqueFileIdentifierFrame::asProperties() const { PropertyMap map; if(d->owner == "http://musicbrainz.org") { - map.insert("MUSICBRAINZ_RECORDINGID", String(d->identifier)); + map.insert("MUSICBRAINZ_TRACKID", String(d->identifier)); } else { map.unsupportedData().append(frameID() + String("/") + d->owner); diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 372778b1..37d96846 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -127,7 +127,7 @@ Frame *Frame::createTextualFrame(const String &key, const StringList &values) // return frame; } } - if(key == "MUSICBRAINZ_RECORDINGID" && values.size() == 1) { + if(key == "MUSICBRAINZ_TRACKID" && values.size() == 1) { UniqueFileIdentifierFrame *frame = new UniqueFileIdentifierFrame("http://musicbrainz.org", values.front().data(String::UTF8)); return frame; } @@ -394,9 +394,9 @@ static const char *frameTranslation[][2] = { static const TagLib::uint txxxFrameTranslationSize = 7; static const char *txxxFrameTranslation[][2] = { - { "MusicBrainz Album Id", "MUSICBRAINZ_RELEASEID" }, + { "MusicBrainz Album Id", "MUSICBRAINZ_ALBUMID" }, { "MusicBrainz Artist Id", "MUSICBRAINZ_ARTISTID" }, - { "MusicBrainz Album Artist Id", "MUSICBRAINZ_RELEASEARTISTID" }, + { "MusicBrainz Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, { "MusicBrainz Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, { "MusicBrainz Work Id", "MUSICBRAINZ_WORKID" }, { "Acoustid Id", "ACOUSTID_ID" }, diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 8073003e..c6d19fef 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -78,12 +78,12 @@ namespace TagLib { * * MusicBrainz identifiers: * - * - MUSICBRAINZ_RECORDINGID - * - MUSICBRAINZ_RELEASEID + * - MUSICBRAINZ_TRACKID + * - MUSICBRAINZ_ALBUMID * - MUSICBRAINZ_RELEASEGROUPID * - MUSICBRAINZ_WORKID * - MUSICBRAINZ_ARTISTID - * - MUSICBRAINZ_RELEASEARTISTID + * - MUSICBRAINZ_ALBUMARTISTID * - ACOUSTID_ID * - ACOUSTID_FINGERPRINT * - MUSICIP_PUID diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index b48bd68f..48faf306 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -683,11 +683,11 @@ public: CPPUNIT_ASSERT(properties.contains("LYRICS")); CPPUNIT_ASSERT(properties.contains("LYRICS:TEST")); - CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RECORDINGID")); - CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_RECORDINGID"].front()); + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_TRACKID")); + CPPUNIT_ASSERT_EQUAL(String("152454b9-19ba-49f3-9fc9-8fc26545cf41"), properties["MUSICBRAINZ_TRACKID"].front()); - CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_RELEASEID")); - CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_RELEASEID"].front()); + CPPUNIT_ASSERT(properties.contains("MUSICBRAINZ_ALBUMID")); + CPPUNIT_ASSERT_EQUAL(String("95c454a5-d7e0-4d8f-9900-db04aca98ab3"), properties["MUSICBRAINZ_ALBUMID"].front()); tag.removeUnsupportedProperties(properties.unsupportedData()); CPPUNIT_ASSERT(tag.frameList("APIC").isEmpty()); From 1b813d9d6cd4087af86179fd317a2d6fae8c1bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Wed, 21 Nov 2012 17:26:17 +0100 Subject: [PATCH 08/10] Document sort names --- taglib/toolkit/tpropertymap.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index c6d19fef..d67eea88 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -58,6 +58,13 @@ namespace TagLib { * - GENRE * - COMMENT * + * Sort names: + * + * - TITLESORT + * - ALBUMSORT + * - ARTISTSORT + * - ALBUMARTISTSORT + * * Credits: * * - COMPOSER From 353eb9f00f35f2e9498968de2cf07ce2f7845f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Thu, 22 Nov 2012 10:40:22 +0100 Subject: [PATCH 09/10] Implement the PropertyMap interface for MP4 --- taglib/ape/apefile.h | 1 + taglib/mp4/mp4file.cpp | 16 ++++ taglib/mp4/mp4file.h | 16 ++++ taglib/mp4/mp4tag.cpp | 151 +++++++++++++++++++++++++++++++ taglib/mp4/mp4tag.h | 4 + taglib/mpeg/id3v2/id3v2frame.cpp | 2 +- taglib/toolkit/tfile.cpp | 10 +- taglib/toolkit/tpropertymap.h | 3 + tests/test_mp4.cpp | 51 +++++++++++ 9 files changed, 250 insertions(+), 4 deletions(-) diff --git a/taglib/ape/apefile.h b/taglib/ape/apefile.h index 8b187f6a..7be0f21d 100644 --- a/taglib/ape/apefile.h +++ b/taglib/ape/apefile.h @@ -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. diff --git a/taglib/mp4/mp4file.cpp b/taglib/mp4/mp4file.cpp index a9543211..f41ee888 100644 --- a/taglib/mp4/mp4file.cpp +++ b/taglib/mp4/mp4file.cpp @@ -29,6 +29,7 @@ #include #include +#include #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 { diff --git a/taglib/mp4/mp4file.h b/taglib/mp4/mp4file.h index 2ed3bea5..17fd5a95 100644 --- a/taglib/mp4/mp4file.h +++ b/taglib/mp4/mp4file.h @@ -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. */ diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index afecf98a..3c22f062 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -29,6 +29,7 @@ #include #include +#include #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 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 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; +} + diff --git a/taglib/mp4/mp4tag.h b/taglib/mp4/mp4tag.h index b5ea6ebb..0e1d0676 100644 --- a/taglib/mp4/mp4tag.h +++ b/taglib/mp4/mp4tag.h @@ -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); diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 37d96846..f94a074e 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -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" }, diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 8d7ccdc9..24579496 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -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(this)->properties(); if(dynamic_cast(this)) return dynamic_cast(this)->properties(); + if(dynamic_cast(this)) + return dynamic_cast(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(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); else tag()->removeUnsupportedProperties(properties); } @@ -231,6 +233,8 @@ PropertyMap File::setProperties(const PropertyMap &properties) return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); + else if(dynamic_cast(this)) + return dynamic_cast(this)->setProperties(properties); else return tag()->setProperties(properties); } diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index d67eea88..51d5df6f 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -82,6 +82,9 @@ namespace TagLib { * - ENCODEDBY * - MOOD * - COMMENT + * - MEDIA + * - CATALOGNUMBER + * - BARCODE * * MusicBrainz identifiers: * diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index d6830a61..be7ad2c3 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -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); From 812f63502b6b1aaf475edb555458b73357eee7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Fri, 23 Nov 2012 09:32:00 +0100 Subject: [PATCH 10/10] Implement the PropertyMap interface for WMA --- taglib/asf/asffile.cpp | 16 ++++ taglib/asf/asffile.h | 16 ++++ taglib/asf/asftag.cpp | 160 +++++++++++++++++++++++++++++++ taglib/asf/asftag.h | 4 + taglib/mpeg/id3v2/id3v2frame.cpp | 2 +- taglib/toolkit/tfile.cpp | 9 +- taglib/toolkit/tpropertymap.h | 1 + tests/test_asf.cpp | 39 +++++++- 8 files changed, 241 insertions(+), 6 deletions(-) diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index 96b8706f..eb000924 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include "asffile.h" #include "asftag.h" @@ -403,6 +404,21 @@ ASF::Tag *ASF::File::tag() const return d->tag; } +PropertyMap ASF::File::properties() const +{ + return d->tag->properties(); +} + +void ASF::File::removeUnsupportedProperties(const StringList &properties) +{ + d->tag->removeUnsupportedProperties(properties); +} + +PropertyMap ASF::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + ASF::Properties *ASF::File::audioProperties() const { return d->properties; diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index 3a7eef56..7d0e3bc7 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -90,6 +90,22 @@ namespace TagLib { */ virtual 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 ASF audio properties for this file. */ diff --git a/taglib/asf/asftag.cpp b/taglib/asf/asftag.cpp index b7b541fc..1cbd16eb 100644 --- a/taglib/asf/asftag.cpp +++ b/taglib/asf/asftag.cpp @@ -27,6 +27,7 @@ #include #endif +#include #include "asftag.h" using namespace TagLib; @@ -196,3 +197,162 @@ bool ASF::Tag::isEmpty() const d->attributeListMap.isEmpty(); } +static const char *keyTranslation[][2] = { + { "WM/AlbumTitle", "ALBUM" }, + { "WM/Composer", "COMPOSER" }, + { "WM/Writer", "WRITER" }, + { "WM/Conductor", "CONDUCTOR" }, + { "WM/ModifiedBy", "REMIXER" }, + { "WM/Year", "DATE" }, + { "WM/OriginalReleaseYear", "ORIGINALDATE" }, + { "WM/Producer", "PRODUCER" }, + { "WM/ContentGroupDescription", "GROUPING" }, + { "WM/SubTitle", "SUBTITLE" }, + { "WM/SetSubTitle", "DISCSUBTITLE" }, + { "WM/TrackNumber", "TRACKNUMBER" }, + { "WM/PartOfSet", "DISCNUMBER" }, + { "WM/Genre", "GENRE" }, + { "WM/BeatsPerMinute", "BPM" }, + { "WM/Mood", "MOOD" }, + { "WM/ISRC", "ISRC" }, + { "WM/Lyrics", "LYRICS" }, + { "WM/Media", "MEDIA" }, + { "WM/Publisher", "LABEL" }, + { "WM/CatalogNo", "CATALOGNUMBER" }, + { "WM/Barcode", "BARCODE" }, + { "WM/EncodedBy", "ENCODEDBY" }, + { "WM/AlbumSortOrder", "ALBUMSORT" }, + { "WM/AlbumArtistSortOrder", "ALBUMARTISTSORT" }, + { "WM/ArtistSortOrder", "ARTISTSORT" }, + { "WM/TitleSortOrder", "TITLESORT" }, + { "WM/Script", "SCRIPT" }, + { "WM/Language", "LANGUAGE" }, + { "MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID" }, + { "MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID" }, + { "MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID" }, + { "MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID" }, + { "MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID" }, + { "MusicBrainz/Work Id", "MUSICBRAINZ_WORKID" }, + { "MusicIP/PUID", "MUSICIP_PUID" }, + { "Acoustid/Id", "ACOUSTID_ID" }, + { "Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT" }, +}; + +PropertyMap ASF::Tag::properties() const +{ + static Map 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; + + if(!d->title.isEmpty()) { + props["TITLE"] = d->title; + } + if(!d->artist.isEmpty()) { + props["ARTIST"] = d->artist; + } + if(!d->copyright.isEmpty()) { + props["COPYRIGHT"] = d->copyright; + } + if(!d->comment.isEmpty()) { + props["COMMENT"] = d->comment; + } + + ASF::AttributeListMap::ConstIterator it = d->attributeListMap.begin(); + for(; it != d->attributeListMap.end(); ++it) { + if(keyMap.contains(it->first)) { + String key = keyMap[it->first]; + AttributeList::ConstIterator it2 = it->second.begin(); + for(; it2 != it->second.end(); ++it2) { + if(key == "TRACKNUMBER") { + if(it2->type() == ASF::Attribute::DWordType) + props.insert(key, String::number(it2->toUInt())); + else + props.insert(key, it2->toString()); + } + else { + props.insert(key, it2->toString()); + } + } + } + else { + props.unsupportedData().append(it->first); + } + } + return props; +} + +void ASF::Tag::removeUnsupportedProperties(const StringList &props) +{ + StringList::ConstIterator it = props.begin(); + for(; it != props.end(); ++it) + d->attributeListMap.erase(*it); +} + +PropertyMap ASF::Tag::setProperties(const PropertyMap &props) +{ + static Map 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()) { + if(it->first == "TITLE") { + d->title = String::null; + } + else if(it->first == "ARTIST") { + d->artist = String::null; + } + else if(it->first == "COMMENT") { + d->comment = String::null; + } + else if(it->first == "COPYRIGHT") { + d->copyright = String::null; + } + else { + d->attributeListMap.erase(reverseKeyMap[it->first]); + } + } + } + + PropertyMap ignoredProps; + it = props.begin(); + for(; it != props.end(); ++it) { + if(reverseKeyMap.contains(it->first)) { + String name = reverseKeyMap[it->first]; + removeItem(name); + StringList::ConstIterator it2 = it->second.begin(); + for(; it2 != it->second.end(); ++it2) { + addAttribute(name, *it2); + } + } + else if(it->first == "TITLE") { + d->title = it->second.toString(); + } + else if(it->first == "ARTIST") { + d->artist = it->second.toString(); + } + else if(it->first == "COMMENT") { + d->comment = it->second.toString(); + } + else if(it->first == "COPYRIGHT") { + d->copyright = it->second.toString(); + } + else { + ignoredProps.insert(it->first, it->second); + } + } + + return ignoredProps; +} diff --git a/taglib/asf/asftag.h b/taglib/asf/asftag.h index 6b2d2bf3..f68579d8 100644 --- a/taglib/asf/asftag.h +++ b/taglib/asf/asftag.h @@ -176,6 +176,10 @@ namespace TagLib { */ void addAttribute(const String &name, const Attribute &attribute); + PropertyMap properties() const; + void removeUnsupportedProperties(const StringList& properties); + PropertyMap setProperties(const PropertyMap &properties); + private: class TagPrivate; diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index f94a074e..e2b84311 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -368,7 +368,7 @@ static const char *frameTranslation[][2] = { { "TPE4", "REMIXER" }, // could also be ARRANGER { "TPOS", "DISCNUMBER" }, { "TPRO", "PRODUCEDNOTICE" }, - { "TPUB", "PUBLISHER" }, + { "TPUB", "LABEL" }, { "TRCK", "TRACKNUMBER" }, { "TRSN", "RADIOSTATION" }, { "TRSO", "RADIOSTATIONOWNER" }, diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index 24579496..7251f3f7 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -155,9 +155,8 @@ PropertyMap File::properties() const return dynamic_cast(this)->properties(); if(dynamic_cast(this)) return dynamic_cast(this)->properties(); - // no specialized implementation available -> use generic one - // - ASF: ugly format, largely undocumented, not worth implementing - // dict interface ... + if(dynamic_cast(this)) + return dynamic_cast(this)->properties(); return tag()->properties(); } @@ -195,6 +194,8 @@ void File::removeUnsupportedProperties(const StringList &properties) dynamic_cast(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); + else if(dynamic_cast(this)) + dynamic_cast(this)->removeUnsupportedProperties(properties); else tag()->removeUnsupportedProperties(properties); } @@ -235,6 +236,8 @@ PropertyMap File::setProperties(const PropertyMap &properties) return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); + else if(dynamic_cast(this)) + return dynamic_cast(this)->setProperties(properties); else return tag()->setProperties(properties); } diff --git a/taglib/toolkit/tpropertymap.h b/taglib/toolkit/tpropertymap.h index 51d5df6f..2be49ddb 100644 --- a/taglib/toolkit/tpropertymap.h +++ b/taglib/toolkit/tpropertymap.h @@ -83,6 +83,7 @@ namespace TagLib { * - MOOD * - COMMENT * - MEDIA + * - LABEL * - CATALOGNUMBER * - BARCODE * diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 0cd1f3d7..8610c24b 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include "utils.h" @@ -13,7 +14,7 @@ using namespace TagLib; class TestASF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestASF); - CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testAudioProperties); CPPUNIT_TEST(testRead); CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testSaveStream); @@ -22,11 +23,12 @@ class TestASF : public CppUnit::TestFixture CPPUNIT_TEST(testSaveLargeValue); CPPUNIT_TEST(testSavePicture); CPPUNIT_TEST(testSaveMultiplePictures); + CPPUNIT_TEST(testProperties); CPPUNIT_TEST_SUITE_END(); public: - void testProperties() + void testAudioProperties() { ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); CPPUNIT_ASSERT_EQUAL(4, f.audioProperties()->length()); @@ -215,6 +217,39 @@ public: delete f; } + void testProperties() + { + ASF::File f(TEST_FILE_PATH_C("silence-1.wma")); + + PropertyMap tags = f.properties(); + + tags["TRACKNUMBER"] = StringList("2"); + tags["DISCNUMBER"] = StringList("3"); + tags["BPM"] = StringList("123"); + tags["ARTIST"] = StringList("Foo Bar"); + f.setProperties(tags); + + tags = f.properties(); + + CPPUNIT_ASSERT_EQUAL(String("Foo Bar"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL(StringList("Foo Bar"), tags["ARTIST"]); + + CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/BeatsPerMinute")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/BeatsPerMinute"].size()); + CPPUNIT_ASSERT_EQUAL(String("123"), f.tag()->attributeListMap()["WM/BeatsPerMinute"].front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("123"), tags["BPM"]); + + CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/TrackNumber")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/TrackNumber"].size()); + CPPUNIT_ASSERT_EQUAL(String("2"), f.tag()->attributeListMap()["WM/TrackNumber"].front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("2"), tags["TRACKNUMBER"]); + + CPPUNIT_ASSERT(f.tag()->attributeListMap().contains("WM/PartOfSet")); + CPPUNIT_ASSERT_EQUAL(1u, f.tag()->attributeListMap()["WM/PartOfSet"].size()); + CPPUNIT_ASSERT_EQUAL(String("3"), f.tag()->attributeListMap()["WM/PartOfSet"].front().toString()); + CPPUNIT_ASSERT_EQUAL(StringList("3"), tags["DISCNUMBER"]); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestASF);