ASF: Improve PropertyMap interface (#1401)

Support more property keys, also with numeric and Guid types.

---------

Co-authored-by: Urs Fleisch <[email protected]>
This commit is contained in:
MSOB7Y
2026-08-09 15:44:18 +02:00
committed by GitHub
co-authored by Urs Fleisch
parent 2a5fc12b68
commit 0836f213e7
6 changed files with 382 additions and 76 deletions
+7
View File
@@ -162,6 +162,13 @@ ASF::Picture ASF::Attribute::toPicture() const
return d->pictureValue;
}
ASF::Attribute ASF::Attribute::fromGuid(const ByteVector& guid)
{
Attribute attr(guid);
attr.d->type = GuidType;
return attr;
}
String ASF::Attribute::parse(ASF::File &file, int kind)
{
unsigned int size, nameLength;
+5
View File
@@ -165,6 +165,11 @@ namespace TagLib
*/
Picture toPicture() const;
/*!
* Create an attribute holding a Guid.
*/
static Attribute fromGuid(const ByteVector &guid);
/*!
* Returns the language number, or 0 is no stream number was set.
*/
+182 -74
View File
@@ -26,6 +26,7 @@
#include "asftag.h"
#include <array>
#include <string>
#include <utility>
#include "tpropertymap.h"
@@ -228,71 +229,139 @@ bool ASF::Tag::isEmpty() const
namespace
{
constexpr std::array keyTranslation {
std::pair("WM/AlbumTitle", "ALBUM"),
std::pair("WM/AlbumArtist", "ALBUMARTIST"),
std::pair("WM/AuthorURL", "ARTISTWEBPAGE"),
std::pair("WM/Composer", "COMPOSER"),
std::pair("WM/Writer", "LYRICIST"),
std::pair("WM/Conductor", "CONDUCTOR"),
std::pair("WM/ModifiedBy", "REMIXER"),
std::pair("WM/Year", "DATE"),
std::pair("WM/OriginalAlbumTitle", "ORIGINALALBUM"),
std::pair("WM/OriginalArtist", "ORIGINALARTIST"),
std::pair("WM/OriginalFilename", "ORIGINALFILENAME"),
std::pair("WM/OriginalLyricist", "ORIGINALLYRICIST"),
std::pair("WM/OriginalReleaseYear", "ORIGINALDATE"),
std::pair("WM/Producer", "PRODUCER"),
std::pair("WM/ContentGroupDescription", "WORK"),
std::pair("WM/SubTitle", "SUBTITLE"),
std::pair("WM/SetSubTitle", "DISCSUBTITLE"),
std::pair("WM/TrackNumber", "TRACKNUMBER"),
std::pair("WM/PartOfSet", "DISCNUMBER"),
std::pair("WM/Genre", "GENRE"),
std::pair("WM/BeatsPerMinute", "BPM"),
std::pair("WM/Mood", "MOOD"),
std::pair("WM/InitialKey", "INITIALKEY"),
std::pair("WM/ISRC", "ISRC"),
std::pair("WM/Lyrics", "LYRICS"),
std::pair("WM/Media", "MEDIA"),
std::pair("WM/Publisher", "LABEL"),
std::pair("WM/CatalogNo", "CATALOGNUMBER"),
std::pair("WM/Barcode", "BARCODE"),
std::pair("WM/EncodedBy", "ENCODEDBY"),
std::pair("WM/EncodingSettings", "ENCODING"),
std::pair("WM/EncodingTime", "ENCODINGTIME"),
std::pair("WM/AudioFileURL", "FILEWEBPAGE"),
std::pair("WM/AlbumSortOrder", "ALBUMSORT"),
std::pair("WM/AlbumArtistSortOrder", "ALBUMARTISTSORT"),
std::pair("WM/ArtistSortOrder", "ARTISTSORT"),
std::pair("WM/TitleSortOrder", "TITLESORT"),
std::pair("WM/Script", "SCRIPT"),
std::pair("WM/Language", "LANGUAGE"),
std::pair("WM/ARTISTS", "ARTISTS"),
std::pair("ASIN", "ASIN"),
std::pair("MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID"),
std::pair("MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID"),
std::pair("MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID"),
std::pair("MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID"),
std::pair("MusicBrainz/Album Release Country", "RELEASECOUNTRY"),
std::pair("MusicBrainz/Album Status", "RELEASESTATUS"),
std::pair("MusicBrainz/Album Type", "RELEASETYPE"),
std::pair("MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID"),
std::pair("MusicBrainz/Release Track Id", "MUSICBRAINZ_RELEASETRACKID"),
std::pair("MusicBrainz/Work Id", "MUSICBRAINZ_WORKID"),
std::pair("MusicIP/PUID", "MUSICIP_PUID"),
std::pair("Acoustid/Id", "ACOUSTID_ID"),
std::pair("Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT"),
std::tuple("WM/AlbumTitle", "ALBUM", ASF::Attribute::UnicodeType),
std::tuple("WM/AlbumArtist", "ALBUMARTIST", ASF::Attribute::UnicodeType),
std::tuple("WM/AuthorURL", "ARTISTWEBPAGE", ASF::Attribute::UnicodeType),
std::tuple("WM/Composer", "COMPOSER", ASF::Attribute::UnicodeType),
std::tuple("WM/Writer", "LYRICIST", ASF::Attribute::UnicodeType),
std::tuple("WM/Conductor", "CONDUCTOR", ASF::Attribute::UnicodeType),
std::tuple("WM/ModifiedBy", "REMIXER", ASF::Attribute::UnicodeType),
std::tuple("WM/Year", "DATE", ASF::Attribute::UnicodeType),
std::tuple("WM/OriginalAlbumTitle", "ORIGINALALBUM", ASF::Attribute::UnicodeType),
std::tuple("WM/OriginalArtist", "ORIGINALARTIST", ASF::Attribute::UnicodeType),
std::tuple("WM/OriginalFilename", "ORIGINALFILENAME", ASF::Attribute::UnicodeType),
std::tuple("WM/OriginalLyricist", "ORIGINALLYRICIST", ASF::Attribute::UnicodeType),
std::tuple("WM/OriginalReleaseYear", "ORIGINALDATE", ASF::Attribute::UnicodeType),
std::tuple("WM/Producer", "PRODUCER", ASF::Attribute::UnicodeType),
std::tuple("WM/ContentGroupDescription", "WORK", ASF::Attribute::UnicodeType),
std::tuple("WM/SubTitle", "SUBTITLE", ASF::Attribute::UnicodeType),
std::tuple("WM/SetSubTitle", "DISCSUBTITLE", ASF::Attribute::UnicodeType),
std::tuple("WM/TrackNumber", "TRACKNUMBER", ASF::Attribute::UnicodeType),
std::tuple("WM/PartOfSet", "DISCNUMBER", ASF::Attribute::UnicodeType),
std::tuple("WM/Genre", "GENRE", ASF::Attribute::UnicodeType),
std::tuple("WM/BeatsPerMinute", "BPM", ASF::Attribute::UnicodeType),
std::tuple("WM/Mood", "MOOD", ASF::Attribute::UnicodeType),
std::tuple("WM/InitialKey", "INITIALKEY", ASF::Attribute::UnicodeType),
std::tuple("WM/ISRC", "ISRC", ASF::Attribute::UnicodeType),
std::tuple("WM/Lyrics", "LYRICS", ASF::Attribute::UnicodeType),
std::tuple("WM/Media", "MEDIA", ASF::Attribute::UnicodeType),
std::tuple("WM/Publisher", "LABEL", ASF::Attribute::UnicodeType),
std::tuple("WM/CatalogNo", "CATALOGNUMBER", ASF::Attribute::UnicodeType),
std::tuple("WM/Barcode", "BARCODE", ASF::Attribute::UnicodeType),
std::tuple("WM/EncodedBy", "ENCODEDBY", ASF::Attribute::UnicodeType),
std::tuple("WM/EncodingSettings", "ENCODING", ASF::Attribute::UnicodeType),
std::tuple("WM/EncodingTime", "ENCODINGTIME", ASF::Attribute::QWordType),
std::tuple("WM/AudioFileURL", "FILEWEBPAGE", ASF::Attribute::UnicodeType),
std::tuple("WM/AlbumSortOrder", "ALBUMSORT", ASF::Attribute::UnicodeType),
std::tuple("WM/AlbumArtistSortOrder", "ALBUMARTISTSORT", ASF::Attribute::UnicodeType),
std::tuple("WM/ArtistSortOrder", "ARTISTSORT", ASF::Attribute::UnicodeType),
std::tuple("WM/TitleSortOrder", "TITLESORT", ASF::Attribute::UnicodeType),
std::tuple("WM/Script", "SCRIPT", ASF::Attribute::UnicodeType),
std::tuple("WM/Language", "LANGUAGE", ASF::Attribute::UnicodeType),
std::tuple("WM/ARTISTS", "ARTISTS", ASF::Attribute::UnicodeType),
std::tuple("ASIN", "ASIN", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Track Id", "MUSICBRAINZ_TRACKID", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Artist Id", "MUSICBRAINZ_ARTISTID", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Album Id", "MUSICBRAINZ_ALBUMID", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Album Artist Id", "MUSICBRAINZ_ALBUMARTISTID", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Album Release Country", "RELEASECOUNTRY", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Album Status", "RELEASESTATUS", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Album Type", "RELEASETYPE", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Release Group Id", "MUSICBRAINZ_RELEASEGROUPID", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Release Track Id", "MUSICBRAINZ_RELEASETRACKID", ASF::Attribute::UnicodeType),
std::tuple("MusicBrainz/Work Id", "MUSICBRAINZ_WORKID", ASF::Attribute::UnicodeType),
std::tuple("MusicIP/PUID", "MUSICIP_PUID", ASF::Attribute::UnicodeType),
std::tuple("Acoustid/Id", "ACOUSTID_ID", ASF::Attribute::UnicodeType),
std::tuple("Acoustid/Fingerprint", "ACOUSTID_FINGERPRINT", ASF::Attribute::UnicodeType),
std::tuple("replaygain_track_gain", "REPLAYGAIN_TRACK_GAIN", ASF::Attribute::UnicodeType),
std::tuple("replaygain_track_peak", "REPLAYGAIN_TRACK_PEAK", ASF::Attribute::UnicodeType),
std::tuple("replaygain_album_gain", "REPLAYGAIN_ALBUM_GAIN", ASF::Attribute::UnicodeType),
std::tuple("replaygain_album_peak", "REPLAYGAIN_ALBUM_PEAK", ASF::Attribute::UnicodeType),
std::tuple("WM/MediaClassPrimaryID", "MEDIACLASSPRIMARYID", ASF::Attribute::GuidType),
std::tuple("WM/MediaClassSecondaryID", "MEDIACLASSSECONDARYID", ASF::Attribute::GuidType),
std::tuple("WM/WMCollectionGroupID", "COLLECTIONGROUPID", ASF::Attribute::GuidType),
std::tuple("WM/WMCollectionID", "COLLECTIONID", ASF::Attribute::GuidType),
std::tuple("WM/WMContentID", "CONTENTID", ASF::Attribute::GuidType),
std::tuple("WM/ContentDistributor", "CONTENTDISTRIBUTOR", ASF::Attribute::UnicodeType),
std::tuple("WM/ParentalRating", "PARENTALRATING", ASF::Attribute::UnicodeType),
std::tuple("WM/Period", "PERIOD", ASF::Attribute::UnicodeType),
std::tuple("WM/PromotionURL", "PROMOTIONURL", ASF::Attribute::UnicodeType),
std::tuple("WM/ToolName", "TOOLNAME", ASF::Attribute::UnicodeType),
std::tuple("WM/ToolVersion", "TOOLVERSION", ASF::Attribute::UnicodeType),
std::tuple("WM/Provider", "PROVIDER", ASF::Attribute::UnicodeType),
std::tuple("WM/UniqueFileIdentifier", "UNIQUEFILEIDENTIFIER", ASF::Attribute::UnicodeType),
std::tuple("WMFSDKVersion", "WMFSDKVERSION", ASF::Attribute::UnicodeType),
std::tuple("WMFSDKNeeded", "WMFSDKNEEDED", ASF::Attribute::UnicodeType),
std::tuple("DeviceConformanceTemplate", "DEVICECONFORMANCETEMPLATE", ASF::Attribute::UnicodeType),
std::tuple("MediaFoundationVersion", "MEDIAFOUNDATIONVERSION", ASF::Attribute::UnicodeType),
std::tuple("IsVBR", "ISVBR", ASF::Attribute::BoolType),
std::tuple("PeakValue", "PEAKVALUE", ASF::Attribute::DWordType),
std::tuple("AverageLevel", "AVERAGELEVEL", ASF::Attribute::DWordType),
};
// Attribute names are matched case-insensitively; taggers disagree on the
// casing of names not defined by Windows Media (e.g. replaygain_track_gain).
String translateKey(const String &key)
{
for(const auto &[k, t] : keyTranslation) {
if(key == k)
return t;
const String upperKey = key.upper();
for(const auto &[k, v, t] : keyTranslation) {
if(upperKey == String(k).upper())
return v;
}
return String();
}
void eraseAttribute(ASF::AttributeListMap &attributeListMap, const String &name)
{
const String upperName = name.upper();
StringList keys;
for(const auto &[k, attributes] : std::as_const(attributeListMap)) {
if(k.upper() == upperName)
keys.append(k);
}
for(const auto &k : keys)
attributeListMap.erase(k);
}
String attributeToString(const ASF::Attribute &attr)
{
switch(attr.type()) {
case ASF::Attribute::WordType:
case ASF::Attribute::DWordType:
case ASF::Attribute::QWordType:
case ASF::Attribute::BoolType:
return String(std::to_string(attr.toULongLong()));
case ASF::Attribute::GuidType:
if(const ByteVector data = attr.toByteVector(); data.size() == 16) {
String str;
for(int i = 0; i < 16; ++i) {
if(i == 4 || i == 6 || i == 8 || i == 10) {
str += '-';
}
const auto c = static_cast<unsigned char>(data[i]);
unsigned char d = c >> 4;
str += static_cast<char>(d >= 10 ? d - 10 + 'A' : d + '0');
d = c & 0x0f;
str += static_cast<char>(d >= 10 ? d - 10 + 'A' : d + '0');
}
return str;
}
return {};
default:
return attr.toString();
}
}
} // namespace
PropertyMap ASF::Tag::properties() const
@@ -315,14 +384,12 @@ PropertyMap ASF::Tag::properties() const
for(const auto &[k, attributes] : std::as_const(d->attributeListMap)) {
if(const String key = translateKey(k); !key.isEmpty()) {
for(const auto &attr : attributes) {
if(key == "TRACKNUMBER") {
if(attr.type() == ASF::Attribute::DWordType)
props.insert(key, String::number(attr.toUInt()));
else
props.insert(key, attr.toString());
}
else {
props.insert(key, attr.toString());
// The same attribute can occur in both the extended content
// description object and the metadata (library) object, skip exact
// duplicates (e.g. a second identical IsVBR).
if(const String value = attributeToString(attr);
!props.value(key).contains(value)) {
props.insert(key, value);
}
}
}
@@ -341,10 +408,10 @@ void ASF::Tag::removeUnsupportedProperties(const StringList &props)
PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
{
static const Map<String, String> reverseKeyMap = [] {
Map<String, String> map;
for(const auto &[k, t] : keyTranslation) {
map[t] = k;
static const Map<String, std::pair<String, Attribute::AttributeTypes>> reverseKeyMap = [] {
Map<String, std::pair<String, Attribute::AttributeTypes>> map;
for(const auto &[k, v, t] : keyTranslation) {
map[v] = {k, t};
}
return map;
}();
@@ -365,7 +432,7 @@ PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
d->copyright.clear();
}
else {
d->attributeListMap.erase(reverseKeyMap[prop]);
eraseAttribute(d->attributeListMap, reverseKeyMap[prop].first);
}
}
}
@@ -373,10 +440,51 @@ PropertyMap ASF::Tag::setProperties(const PropertyMap &props)
PropertyMap ignoredProps;
for(const auto &[prop, attributes] : props) {
if(reverseKeyMap.contains(prop)) {
String name = reverseKeyMap[prop];
removeItem(name);
for(const auto &attr : attributes) {
addAttribute(name, attr);
const auto &[name, type] = reverseKeyMap[prop];
eraseAttribute(d->attributeListMap, name);
for(const auto &str : attributes) {
switch(type) {
case Attribute::WordType:
addAttribute(name, static_cast<unsigned short>(str.toULongLong()));
break;
case Attribute::DWordType:
addAttribute(name, static_cast<unsigned int>(str.toULongLong()));
break;
case Attribute::QWordType:
addAttribute(name, str.toULongLong());
break;
case Attribute::BoolType:
addAttribute(name, !str.isEmpty() && str != "0" && str.upper() != "TRUE");
break;
case Attribute::GuidType: {
ByteVector data;
String hexStr;
for(wchar_t c : str) {
if(c >= 'a' && c <= 'f') {
hexStr += static_cast<wchar_t>(c + 'A' - 'a');
} else if((c >= 'A' && c <= 'F') || (c >= '0' && c <= '9')) {
hexStr += c;
}
}
if(hexStr.length() == 32) {
unsigned char buf[16];
unsigned char* bufPtr = buf;
for(int i = 0; i < 32;) {
auto h = static_cast<unsigned char>(hexStr[i++]);
auto l = static_cast<unsigned char>(hexStr[i++]);
*bufPtr++ = static_cast<unsigned char>(
((h >= 'A' ? h + 10 - 'A' : h - '0') << 4) |
(l >= 'A' ? l + 10 - 'A' : l - '0'));
}
data = ByteVector(reinterpret_cast<char*>(buf), 16);
}
addAttribute(name, Attribute::fromGuid(data));
break;
}
default:
addAttribute(name, str);
break;
}
}
}
else if(prop == "TITLE") {
Binary file not shown.
+186 -2
View File
@@ -52,6 +52,8 @@ class TestASF : public CppUnit::TestFixture
CPPUNIT_TEST(testSaveMultiplePictures);
CPPUNIT_TEST(testProperties);
CPPUNIT_TEST(testPropertiesAllSupported);
CPPUNIT_TEST(testPropertiesRealFile);
CPPUNIT_TEST(testCaseInsensitiveAttributeNames);
CPPUNIT_TEST(testRepeatedSave);
CPPUNIT_TEST_SUITE_END();
@@ -331,7 +333,7 @@ public:
tags["DISCSUBTITLE"] = StringList("Disc Subtitle");
tags["ENCODEDBY"] = StringList("Encoded by");
tags["ENCODING"] = StringList("Encoding");
tags["ENCODINGTIME"] = StringList("2021-01-03 11:52:19");
tags["ENCODINGTIME"] = StringList("131452413620000000");
tags["FILEWEBPAGE"] = StringList("File Webpage");
tags["GENRE"] = StringList("Genre");
tags["WORK"] = StringList("Grouping");
@@ -366,6 +368,30 @@ public:
tags["TITLE"] = StringList("Title");
tags["TITLESORT"] = StringList("Title Sort");
tags["TRACKNUMBER"] = StringList("2/4");
tags["REPLAYGAIN_TRACK_GAIN"] = StringList("-4.25 dB");
tags["REPLAYGAIN_TRACK_PEAK"] = StringList("0.985412");
tags["REPLAYGAIN_ALBUM_GAIN"] = StringList("-3.80 dB");
tags["REPLAYGAIN_ALBUM_PEAK"] = StringList("0.998201");
tags["MEDIACLASSPRIMARYID"] = StringList("D1607DBC-E323-4BE2-86A1-48A42A28441E");
tags["MEDIACLASSSECONDARYID"] = StringList("F24FF731-96FC-4D0F-A2F5-5A3483682B1A");
tags["COLLECTIONGROUPID"] = StringList("A81F04B3-2B1B-4B52-8700-6F8091DF3B41");
tags["COLLECTIONID"] = StringList("3F2504E0-4F89-11D3-9A0C-0305E82C3301");
tags["CONTENTID"] = StringList("6B29FC40-CA47-1067-B31D-00DD010662DA");
tags["CONTENTDISTRIBUTOR"] = StringList("Universal Music Group");
tags["PARENTALRATING"] = StringList("Explicit");
tags["PERIOD"] = StringList("Baroque");
tags["PROMOTIONURL"] = StringList("https://www.artistwebsite.com/store/album-special-edition");
tags["TOOLNAME"] = StringList("Windows Media Encoder");
tags["TOOLVERSION"] = StringList("9.00.00.2980");
tags["PROVIDER"] = StringList("AMG");
tags["UNIQUEFILEIDENTIFIER"] = StringList("AMGI_0000000000012345");
tags["WMFSDKVERSION"] = StringList("12.0.19041.1");
tags["WMFSDKNEEDED"] = StringList("9.0.0.4503");
tags["DEVICECONFORMANCETEMPLATE"] = StringList("L1");
tags["MEDIAFOUNDATIONVERSION"] = StringList("2.0");
tags["ISVBR"] = StringList("1");
tags["PEAKVALUE"] = StringList("32104");
tags["AVERAGELEVEL"] = StringList("2450");
ScopedFileCopy copy("silence-1", ".wma");
{
@@ -385,10 +411,168 @@ public:
{
const ASF::File f(copy.fileName().c_str());
PropertyMap properties = f.properties();
if (tags != properties) {
if(tags != properties) {
CPPUNIT_ASSERT_EQUAL(tags.toString(), properties.toString());
}
CPPUNIT_ASSERT(tags == properties);
const ASF::Tag *asfTag = f.tag();
CPPUNIT_ASSERT(asfTag);
std::map<String, ASF::Attribute::AttributeTypes> expected = {
{"ASIN", ASF::Attribute::UnicodeType},
{"Acoustid/Fingerprint", ASF::Attribute::UnicodeType},
{"Acoustid/Id", ASF::Attribute::UnicodeType},
{"AverageLevel", ASF::Attribute::DWordType},
{"DeviceConformanceTemplate", ASF::Attribute::UnicodeType},
{"IsVBR", ASF::Attribute::BoolType},
{"MediaFoundationVersion", ASF::Attribute::UnicodeType},
{"MusicBrainz/Album Artist Id", ASF::Attribute::UnicodeType},
{"MusicBrainz/Album Id", ASF::Attribute::UnicodeType},
{"MusicBrainz/Album Release Country", ASF::Attribute::UnicodeType},
{"MusicBrainz/Album Status", ASF::Attribute::UnicodeType},
{"MusicBrainz/Album Type", ASF::Attribute::UnicodeType},
{"MusicBrainz/Artist Id", ASF::Attribute::UnicodeType},
{"MusicBrainz/Release Group Id", ASF::Attribute::UnicodeType},
{"MusicBrainz/Release Track Id", ASF::Attribute::UnicodeType},
{"MusicBrainz/Track Id", ASF::Attribute::UnicodeType},
{"MusicBrainz/Work Id", ASF::Attribute::UnicodeType},
{"MusicIP/PUID", ASF::Attribute::UnicodeType},
{"PeakValue", ASF::Attribute::DWordType},
{"WM/ARTISTS", ASF::Attribute::UnicodeType},
{"WM/AlbumArtist", ASF::Attribute::UnicodeType},
{"WM/AlbumArtistSortOrder", ASF::Attribute::UnicodeType},
{"WM/AlbumSortOrder", ASF::Attribute::UnicodeType},
{"WM/AlbumTitle", ASF::Attribute::UnicodeType},
{"WM/ArtistSortOrder", ASF::Attribute::UnicodeType},
{"WM/AudioFileURL", ASF::Attribute::UnicodeType},
{"WM/AuthorURL", ASF::Attribute::UnicodeType},
{"WM/Barcode", ASF::Attribute::UnicodeType},
{"WM/BeatsPerMinute", ASF::Attribute::UnicodeType},
{"WM/CatalogNo", ASF::Attribute::UnicodeType},
{"WM/Composer", ASF::Attribute::UnicodeType},
{"WM/Conductor", ASF::Attribute::UnicodeType},
{"WM/ContentDistributor", ASF::Attribute::UnicodeType},
{"WM/ContentGroupDescription", ASF::Attribute::UnicodeType},
{"WM/EncodedBy", ASF::Attribute::UnicodeType},
{"WM/EncodingSettings", ASF::Attribute::UnicodeType},
{"WM/EncodingTime", ASF::Attribute::QWordType},
{"WM/Genre", ASF::Attribute::UnicodeType},
{"WM/ISRC", ASF::Attribute::UnicodeType},
{"WM/InitialKey", ASF::Attribute::UnicodeType},
{"WM/Language", ASF::Attribute::UnicodeType},
{"WM/Lyrics", ASF::Attribute::UnicodeType},
{"WM/Media", ASF::Attribute::UnicodeType},
{"WM/MediaClassPrimaryID", ASF::Attribute::GuidType},
{"WM/MediaClassSecondaryID", ASF::Attribute::GuidType},
{"WM/ModifiedBy", ASF::Attribute::UnicodeType},
{"WM/Mood", ASF::Attribute::UnicodeType},
{"WM/OriginalAlbumTitle", ASF::Attribute::UnicodeType},
{"WM/OriginalArtist", ASF::Attribute::UnicodeType},
{"WM/OriginalFilename", ASF::Attribute::UnicodeType},
{"WM/OriginalLyricist", ASF::Attribute::UnicodeType},
{"WM/OriginalReleaseYear", ASF::Attribute::UnicodeType},
{"WM/ParentalRating", ASF::Attribute::UnicodeType},
{"WM/PartOfSet", ASF::Attribute::UnicodeType},
{"WM/Period", ASF::Attribute::UnicodeType},
{"WM/Producer", ASF::Attribute::UnicodeType},
{"WM/PromotionURL", ASF::Attribute::UnicodeType},
{"WM/Provider", ASF::Attribute::UnicodeType},
{"WM/Publisher", ASF::Attribute::UnicodeType},
{"WM/Script", ASF::Attribute::UnicodeType},
{"WM/SetSubTitle", ASF::Attribute::UnicodeType},
{"WM/SubTitle", ASF::Attribute::UnicodeType},
{"WM/TitleSortOrder", ASF::Attribute::UnicodeType},
{"WM/ToolName", ASF::Attribute::UnicodeType},
{"WM/ToolVersion", ASF::Attribute::UnicodeType},
{"WM/TrackNumber", ASF::Attribute::UnicodeType},
{"WM/UniqueFileIdentifier", ASF::Attribute::UnicodeType},
{"WM/WMCollectionGroupID", ASF::Attribute::GuidType},
{"WM/WMCollectionID", ASF::Attribute::GuidType},
{"WM/WMContentID", ASF::Attribute::GuidType},
{"WM/Writer", ASF::Attribute::UnicodeType},
{"WM/Year", ASF::Attribute::UnicodeType},
{"WMFSDKNeeded", ASF::Attribute::UnicodeType},
{"WMFSDKVersion", ASF::Attribute::UnicodeType},
{"replaygain_album_gain", ASF::Attribute::UnicodeType},
{"replaygain_album_peak", ASF::Attribute::UnicodeType},
{"replaygain_track_gain", ASF::Attribute::UnicodeType},
{"replaygain_track_peak", ASF::Attribute::UnicodeType},
};
std::map<String, ASF::Attribute::AttributeTypes> actual;
for(const auto &[name, attr] : asfTag->attributeListMap()) {
if(!attr.isEmpty()) {
actual[name] = attr[0].type();
}
}
if(expected != actual) {
auto mapToStrings = [](const std::map<String, ASF::Attribute::AttributeTypes> &map){
StringList result;
for(const auto &[name, attr] : map) {
result.append(name + ":" + String::number(attr));
}
return result;
};
const StringList expectedStrs = mapToStrings(expected);
const StringList actualStrs = mapToStrings(actual);
CPPUNIT_ASSERT_EQUAL(expectedStrs, actualStrs);
}
CPPUNIT_ASSERT(expected == actual);
}
}
void testPropertiesRealFile()
{
ASF::File f(TEST_FILE_PATH_C("real_example.wma"));
PropertyMap tags = f.properties();
CPPUNIT_ASSERT_EQUAL(StringList("Wake Up, Get Up, Get Out There"), tags["TITLE"]);
CPPUNIT_ASSERT_EQUAL(StringList("Shoji Meguro"), tags["ARTIST"]);
CPPUNIT_ASSERT_EQUAL(StringList("Persona 5: Sounds of Rebellion"), tags["ALBUM"]);
CPPUNIT_ASSERT_EQUAL(StringList("-8.27 dB"), tags["REPLAYGAIN_TRACK_GAIN"]);
CPPUNIT_ASSERT_EQUAL(StringList("1.000000"), tags["REPLAYGAIN_TRACK_PEAK"]);
CPPUNIT_ASSERT_EQUAL(StringList("131452413620000000"), tags["ENCODINGTIME"]);
CPPUNIT_ASSERT_EQUAL(StringList("32673"), tags["PEAKVALUE"]);
CPPUNIT_ASSERT_EQUAL(StringList("8315"), tags["AVERAGELEVEL"]);
CPPUNIT_ASSERT_EQUAL(StringList("0"), tags["ISVBR"]);
CPPUNIT_ASSERT_EQUAL(StringList("12.0.15063.332"), tags["WMFSDKVERSION"]);
CPPUNIT_ASSERT_EQUAL(StringList("0.0.0.0000"), tags["WMFSDKNEEDED"]);
CPPUNIT_ASSERT_EQUAL(StringList("User Feedback"), tags["PROVIDER"]);
CPPUNIT_ASSERT_EQUAL(StringList("L1"), tags["DEVICECONFORMANCETEMPLATE"]);
CPPUNIT_ASSERT_EQUAL(StringList("2.112"), tags["MEDIAFOUNDATIONVERSION"]);
CPPUNIT_ASSERT_EQUAL(StringList("1"), tags["TRACKNUMBER"]);
CPPUNIT_ASSERT_EQUAL(StringList("Alternative"), tags["GENRE"]);
CPPUNIT_ASSERT_EQUAL(StringList("Shoji Meguro"), tags["ALBUMARTIST"]);
CPPUNIT_ASSERT_EQUAL(StringList("00000000-0000-0000-0000-000000000000"), tags["COLLECTIONGROUPID"]);
CPPUNIT_ASSERT_EQUAL(StringList("00000000-0000-0000-0000-000000000000"), tags["COLLECTIONID"]);
CPPUNIT_ASSERT_EQUAL(StringList("00000000-0000-0000-0000-000000000000"), tags["CONTENTID"]);
CPPUNIT_ASSERT_EQUAL(StringList("BC7D60D1-23E3-E24B-86A1-48A42A28441E"), tags["MEDIACLASSPRIMARYID"]);
CPPUNIT_ASSERT_EQUAL(StringList("00000000-0000-0000-0000-000000000000"), tags["MEDIACLASSSECONDARYID"]);
CPPUNIT_ASSERT_EQUAL(StringList(";"), tags["UNIQUEFILEIDENTIFIER"]);
}
void testCaseInsensitiveAttributeNames()
{
ScopedFileCopy copy("silence-1", ".wma");
{
ASF::File f(copy.fileName().c_str());
f.tag()->setAttribute("REPLAYGAIN_TRACK_GAIN", String("-6.00 dB"));
f.save();
}
{
ASF::File f(copy.fileName().c_str());
PropertyMap tags = f.properties();
CPPUNIT_ASSERT_EQUAL(StringList("-6.00 dB"), tags["REPLAYGAIN_TRACK_GAIN"]);
// Replacing the value must not leave a second, differently cased
// attribute behind.
tags["REPLAYGAIN_TRACK_GAIN"] = StringList("-7.00 dB");
f.setProperties(tags);
CPPUNIT_ASSERT(!f.tag()->contains("REPLAYGAIN_TRACK_GAIN"));
CPPUNIT_ASSERT_EQUAL(String("-7.00 dB"),
f.tag()->attribute("replaygain_track_gain").front().toString());
tags = f.properties();
CPPUNIT_ASSERT_EQUAL(StringList("-7.00 dB"), tags["REPLAYGAIN_TRACK_GAIN"]);
}
}
+2
View File
@@ -224,6 +224,7 @@ class TestFileRefDetectByContent : public CppUnit::TestFixture
#ifdef TAGLIB_WITH_ASF
CPPUNIT_TEST(testLosslessWma);
CPPUNIT_TEST(testRealExampleWma);
CPPUNIT_TEST(testSilence1Wma);
#endif
@@ -414,6 +415,7 @@ public:
#ifdef TAGLIB_WITH_ASF
// -- ASF::File --
void testLosslessWma() { detectByContent<ASF::File>("lossless.wma"); }
void testRealExampleWma() { detectByContent<ASF::File>("real_example.wma"); }
void testSilence1Wma() { detectByContent<ASF::File>("silence-1.wma"); }
#endif