diff --git a/taglib/asf/asffile.cpp b/taglib/asf/asffile.cpp index e8414ff4..179649fa 100644 --- a/taglib/asf/asffile.cpp +++ b/taglib/asf/asffile.cpp @@ -58,17 +58,18 @@ public: namespace { - static const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); - static const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); - static const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); - static const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); - static const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); - static const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); - static const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); - static const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); - static const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); - static const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); - static const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); + const ByteVector headerGuid("\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + const ByteVector filePropertiesGuid("\xA1\xDC\xAB\x8C\x47\xA9\xCF\x11\x8E\xE4\x00\xC0\x0C\x20\x53\x65", 16); + const ByteVector streamPropertiesGuid("\x91\x07\xDC\xB7\xB7\xA9\xCF\x11\x8E\xE6\x00\xC0\x0C\x20\x53\x65", 16); + const ByteVector contentDescriptionGuid("\x33\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C", 16); + const ByteVector extendedContentDescriptionGuid("\x40\xA4\xD0\xD2\x07\xE3\xD2\x11\x97\xF0\x00\xA0\xC9\x5E\xA8\x50", 16); + const ByteVector headerExtensionGuid("\xb5\x03\xbf_.\xa9\xcf\x11\x8e\xe3\x00\xc0\x0c Se", 16); + const ByteVector metadataGuid("\xEA\xCB\xF8\xC5\xAF[wH\204g\xAA\214D\xFAL\xCA", 16); + const ByteVector metadataLibraryGuid("\224\034#D\230\224\321I\241A\x1d\x13NEpT", 16); + const ByteVector codecListGuid("\x40\x52\xd1\x86\x1d\x31\xd0\x11\xa3\xa4\x00\xa0\xc9\x03\x48\xf6", 16); + const ByteVector contentEncryptionGuid("\xFB\xB3\x11\x22\x23\xBD\xD2\x11\xB4\xB7\x00\xA0\xC9\x55\xFC\x6E", 16); + const ByteVector extendedContentEncryptionGuid("\x14\xE6\x8A\x29\x22\x26 \x17\x4C\xB9\x35\xDA\xE0\x7E\xE9\x28\x9C", 16); + const ByteVector advancedContentEncryptionGuid("\xB6\x9B\x07\x7A\xA4\xDA\x12\x4E\xA5\xCA\x91\xD3\x8D\xC1\x1A\x8D", 16); } class ASF::File::BaseObject @@ -148,6 +149,13 @@ public: ByteVector render(ASF::File *file); }; +class ASF::File::CodecListObject : public ASF::File::BaseObject +{ +public: + ByteVector guid(); + void parse(ASF::File *file, uint size); +}; + ASF::File::HeaderExtensionObject::~HeaderExtensionObject() { for(unsigned int i = 0; i < objects.size(); i++) { @@ -209,6 +217,7 @@ void ASF::File::StreamPropertiesObject::parse(ASF::File *file, uint size) return; } + file->d->properties->setCodec(data.toUShort(54, false)); file->d->properties->setChannels(data.toUShort(56, false)); file->d->properties->setSampleRate(data.toUInt(58, false)); file->d->properties->setBitrate(static_cast(data.toUInt(62, false) * 8.0 / 1000.0 + 0.5)); @@ -377,6 +386,61 @@ ByteVector ASF::File::HeaderExtensionObject::render(ASF::File *file) return BaseObject::render(file); } +ByteVector ASF::File::CodecListObject::guid() +{ + return codecListGuid; +} + +void ASF::File::CodecListObject::parse(ASF::File *file, uint size) +{ + BaseObject::parse(file, size); + if(data.size() <= 20) { + debug("ASF::File::CodecListObject::parse() -- data is too short."); + return; + } + + uint pos = 16; + + const int count = data.toUInt(pos, false); + pos += 4; + + for(int i = 0; i < count; ++i) { + + if(pos >= data.size()) + break; + + const int type = data.toUShort(pos, false); + pos += 2; + + int nameLength = data.toUShort(pos, false); + pos += 2; + + const uint namePos = pos; + pos += nameLength * 2; + + const int descLength = data.toUShort(pos, false); + pos += 2; + + const uint descPos = pos; + pos += descLength * 2; + + const int infoLength = data.toUShort(pos, false); + pos += 2 + infoLength * 2; + + if(type == 2) { + // First audio codec found. + + const String name(data.mid(namePos, nameLength * 2), String::UTF16LE); + file->d->properties->setCodecName(name.stripWhiteSpace()); + + const String desc(data.mid(descPos, descLength * 2), String::UTF16LE); + file->d->properties->setCodecDescription(desc.stripWhiteSpace()); + + break; + } + } +} + //////////////////////////////////////////////////////////////////////////////// // public members //////////////////////////////////////////////////////////////////////////////// @@ -491,6 +555,9 @@ void ASF::File::read(bool /*readProperties*/, Properties::ReadStyle /*properties else if(guid == headerExtensionGuid) { obj = new HeaderExtensionObject(); } + else if(guid == codecListGuid) { + obj = new CodecListObject(); + } else { if(guid == contentEncryptionGuid || guid == extendedContentEncryptionGuid || diff --git a/taglib/asf/asffile.h b/taglib/asf/asffile.h index 94b2d076..30d49bc1 100644 --- a/taglib/asf/asffile.h +++ b/taglib/asf/asffile.h @@ -134,6 +134,7 @@ namespace TagLib { class ContentDescriptionObject; class ExtendedContentDescriptionObject; class HeaderExtensionObject; + class CodecListObject; class MetadataObject; class MetadataLibraryObject; diff --git a/taglib/asf/asfproperties.cpp b/taglib/asf/asfproperties.cpp index 1b7c6ec1..a836da30 100644 --- a/taglib/asf/asfproperties.cpp +++ b/taglib/asf/asfproperties.cpp @@ -38,6 +38,7 @@ public: sampleRate(0), channels(0), bitsPerSample(0), + codec(ASF::Properties::Unknown), encrypted(false) {} int length; @@ -45,6 +46,9 @@ public: int sampleRate; int channels; int bitsPerSample; + ASF::Properties::Codec codec; + String codecName; + String codecDescription; bool encrypted; }; @@ -98,6 +102,21 @@ int ASF::Properties::bitsPerSample() const return d->bitsPerSample; } +ASF::Properties::Codec ASF::Properties::codec() const +{ + return d->codec; +} + +String ASF::Properties::codecName() const +{ + return d->codecName; +} + +String ASF::Properties::codecDescription() const +{ + return d->codecDescription; +} + bool ASF::Properties::isEncrypted() const { return d->encrypted; @@ -137,6 +156,38 @@ void ASF::Properties::setBitsPerSample(int value) d->bitsPerSample = value; } +void ASF::Properties::setCodec(int value) +{ + switch(value) + { + case 0x0160: + d->codec = WMA1; + break; + case 0x0161: + d->codec = WMA2; + break; + case 0x0162: + d->codec = WMA9Pro; + break; + case 0x0163: + d->codec = WMA9Lossless; + break; + default: + d->codec = Unknown; + break; + } +} + +void ASF::Properties::setCodecName(const String &value) +{ + d->codecName = value; +} + +void ASF::Properties::setCodecDescription(const String &value) +{ + d->codecDescription = value; +} + void ASF::Properties::setEncrypted(bool value) { d->encrypted = value; diff --git a/taglib/asf/asfproperties.h b/taglib/asf/asfproperties.h index 5275aa1a..b89349b3 100644 --- a/taglib/asf/asfproperties.h +++ b/taglib/asf/asfproperties.h @@ -40,7 +40,38 @@ namespace TagLib { public: /*! - * Create an instance of ASF::Properties. + * Audio codec types can be used in ASF file. + */ + enum Codec + { + /*! + * Couldn't detect the codec. + */ + Unknown = 0, + + /*! + * Windows Media Audio 1 + */ + WMA1, + + /*! + * Windows Media Audio 2 or above + */ + WMA2, + + /*! + * Windows Media Audio 9 Professional + */ + WMA9Pro, + + /*! + * Windows Media Audio 9 Lossless + */ + WMA9Lossless, + }; + + /*! + * Creates an instance of ASF::Properties. */ Properties(); @@ -85,6 +116,7 @@ namespace TagLib { * Returns the sample rate in Hz. */ virtual int sampleRate() const; + /*! * Returns the number of audio channels. */ @@ -95,6 +127,33 @@ namespace TagLib { */ int bitsPerSample() const; + /*! + * Returns the codec used in the file. + * + * \see codecName() + * \see codecDescription() + */ + Codec codec() const; + + /*! + * Returns the concrete codec name, for example "Windows Media Audio 9.1" + * used in the file if available, otherwise an empty string. + * + * \see codec() + * \see codecDescription() + */ + String codecName() const; + + /*! + * Returns the codec description, typically contains the encoder settings, + * for example "VBR Quality 50, 44kHz, stereo 1-pass VBR" if available, + * otherwise an empty string. + * + * \see codec() + * \see codecName() + */ + String codecDescription() const; + /*! * Returns whether or not the file is encrypted. */ @@ -109,6 +168,9 @@ namespace TagLib { void setSampleRate(int value); void setChannels(int value); void setBitsPerSample(int value); + void setCodec(int value); + void setCodecName(const String &value); + void setCodecDescription(const String &value); void setEncrypted(bool value); #endif diff --git a/tests/data/lossless.wma b/tests/data/lossless.wma new file mode 100644 index 00000000..e29befcc Binary files /dev/null and b/tests/data/lossless.wma differ diff --git a/tests/test_asf.cpp b/tests/test_asf.cpp index 82df4d1d..663ae583 100644 --- a/tests/test_asf.cpp +++ b/tests/test_asf.cpp @@ -15,6 +15,7 @@ class TestASF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestASF); CPPUNIT_TEST(testAudioProperties); + CPPUNIT_TEST(testLosslessProperties); CPPUNIT_TEST(testRead); CPPUNIT_TEST(testSaveMultipleValues); CPPUNIT_TEST(testSaveStream); @@ -39,6 +40,26 @@ public: CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(ASF::Properties::WMA2, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.1"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("64 kbps, 48 kHz, stereo 2-pass CBR"), f.audioProperties()->codecDescription()); + CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); + } + + void testLosslessProperties() + { + ASF::File f(TEST_FILE_PATH_C("lossless.wma")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(3549, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1152, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(ASF::Properties::WMA9Lossless, f.audioProperties()->codec()); + CPPUNIT_ASSERT_EQUAL(String("Windows Media Audio 9.2 Lossless"), f.audioProperties()->codecName()); + CPPUNIT_ASSERT_EQUAL(String("VBR Quality 100, 44 kHz, 2 channel 16 bit 1-pass VBR"), f.audioProperties()->codecDescription()); CPPUNIT_ASSERT_EQUAL(false, f.audioProperties()->isEncrypted()); }