From 5e7b1da632247be528e1a1b95590480c8f15d1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Lalinsk=C3=BD?= Date: Sat, 13 Oct 2012 08:55:23 +0200 Subject: [PATCH] Add support for Ogg Opus --- NEWS | 1 + taglib/CMakeLists.txt | 10 +- taglib/fileref.cpp | 3 + taglib/ogg/opus/opusfile.cpp | 128 ++++++++++++++ taglib/ogg/opus/opusfile.h | 110 ++++++++++++ taglib/ogg/opus/opusproperties.cpp | 161 ++++++++++++++++++ taglib/ogg/opus/opusproperties.h | 96 +++++++++++ taglib/toolkit/tfile.cpp | 7 + tests/CMakeLists.txt | 3 + .../data/correctness_gain_silent_output.opus | Bin 0 -> 35506 bytes tests/test_opus.cpp | 61 +++++++ 11 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 taglib/ogg/opus/opusfile.cpp create mode 100644 taglib/ogg/opus/opusfile.h create mode 100644 taglib/ogg/opus/opusproperties.cpp create mode 100644 taglib/ogg/opus/opusproperties.h create mode 100644 tests/data/correctness_gain_silent_output.opus create mode 100644 tests/test_opus.cpp diff --git a/NEWS b/NEWS index 548e9baf..9432865a 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ TagLib 1.9 (In Development) ========================== + * Added support for the Ogg Opus file format. * Added support for INFO tags in WAV files. * Changed FileStream to use Windows file API. * Included taglib-config.cmd script for Windows. diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index ebb9165d..72712ca2 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -10,6 +10,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/mp4 ${CMAKE_CURRENT_SOURCE_DIR}/ogg/vorbis ${CMAKE_CURRENT_SOURCE_DIR}/ogg/speex + ${CMAKE_CURRENT_SOURCE_DIR}/ogg/opus ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2 ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v2/frames ${CMAKE_CURRENT_SOURCE_DIR}/mpeg/id3v1 @@ -83,6 +84,8 @@ set(tag_HDRS ogg/flac/oggflacfile.h ogg/speex/speexfile.h ogg/speex/speexproperties.h + ogg/opus/opusfile.h + ogg/opus/opusproperties.h flac/flacfile.h flac/flacpicture.h flac/flacproperties.h @@ -220,6 +223,11 @@ set(speex_SRCS ogg/speex/speexproperties.cpp ) +set(opus_SRCS + ogg/opus/opusfile.cpp + ogg/opus/opusproperties.cpp +) + set(trueaudio_SRCS trueaudio/trueaudiofile.cpp trueaudio/trueaudioproperties.cpp @@ -288,7 +296,7 @@ set(tag_LIB_SRCS ${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_SRCS} ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} - ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} + ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} tag.cpp tagunion.cpp fileref.cpp diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 5c42ed4c..859f3155 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -45,6 +45,7 @@ #include "mp4file.h" #include "wavpackfile.h" #include "speexfile.h" +#include "opusfile.h" #include "trueaudiofile.h" #include "aifffile.h" #include "wavfile.h" @@ -252,6 +253,8 @@ File *FileRef::create(FileName fileName, bool readAudioProperties, return new WavPack::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "SPX") return new Ogg::Speex::File(fileName, readAudioProperties, audioPropertiesStyle); + if(ext == "OPUS") + return new Ogg::Opus::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "TTA") return new TrueAudio::File(fileName, readAudioProperties, audioPropertiesStyle); if(ext == "M4A" || ext == "M4R" || ext == "M4B" || ext == "M4P" || ext == "MP4" || ext == "3G2") diff --git a/taglib/ogg/opus/opusfile.cpp b/taglib/ogg/opus/opusfile.cpp new file mode 100644 index 00000000..836f89c9 --- /dev/null +++ b/taglib/ogg/opus/opusfile.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include + +#include +#include + +#include "opusfile.h" + +using namespace TagLib; +using namespace TagLib::Ogg; + +class Opus::File::FilePrivate +{ +public: + FilePrivate() : + comment(0), + properties(0) {} + + ~FilePrivate() + { + delete comment; + delete properties; + } + + Ogg::XiphComment *comment; + Properties *properties; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Opus::File::File(FileName file, bool readProperties, + Properties::ReadStyle propertiesStyle) : Ogg::File(file) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +Opus::File::File(IOStream *stream, bool readProperties, + Properties::ReadStyle propertiesStyle) : Ogg::File(stream) +{ + d = new FilePrivate; + read(readProperties, propertiesStyle); +} + +Opus::File::~File() +{ + delete d; +} + +Ogg::XiphComment *Opus::File::tag() const +{ + return d->comment; +} + +Opus::Properties *Opus::File::audioProperties() const +{ + return d->properties; +} + +bool Opus::File::save() +{ + if(!d->comment) + d->comment = new Ogg::XiphComment; + + setPacket(1, ByteVector("OpusTags", 8) + d->comment->render(false)); + + return Ogg::File::save(); +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Opus::File::read(bool readProperties, Properties::ReadStyle propertiesStyle) +{ + ByteVector opusHeaderData = packet(0); + + if(!opusHeaderData.startsWith("OpusHead")) { + setValid(false); + debug("Opus::File::read() -- invalid Opus identification header"); + return; + } + + ByteVector commentHeaderData = packet(1); + + if(!commentHeaderData.startsWith("OpusTags")) { + setValid(false); + debug("Opus::File::read() -- invalid Opus tags header"); + return; + } + + debug("starts with OpusTags"); + + d->comment = new Ogg::XiphComment(commentHeaderData.mid(8)); + + if(readProperties) + d->properties = new Properties(this, propertiesStyle); +} diff --git a/taglib/ogg/opus/opusfile.h b/taglib/ogg/opus/opusfile.h new file mode 100644 index 00000000..73375af8 --- /dev/null +++ b/taglib/ogg/opus/opusfile.h @@ -0,0 +1,110 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) +***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_OPUSFILE_H +#define TAGLIB_OPUSFILE_H + +#include "oggfile.h" +#include "xiphcomment.h" + +#include "opusproperties.h" + +namespace TagLib { + + namespace Ogg { + + //! A namespace containing classes for Opus metadata + + namespace Opus { + + //! An implementation of Ogg::File with Opus specific methods + + /*! + * This is the central class in the Ogg Opus metadata processing collection + * of classes. It's built upon Ogg::File which handles processing of the Ogg + * logical bitstream and breaking it down into pages which are handled by + * the codec implementations, in this case Opus specifically. + */ + + class TAGLIB_EXPORT File : public Ogg::File + { + public: + /*! + * Contructs a Opus file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + */ + File(FileName file, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Contructs a Opus file from \a file. If \a readProperties is true the + * file's audio properties will also be read using \a propertiesStyle. If + * false, \a propertiesStyle is ignored. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + Properties::ReadStyle propertiesStyle = Properties::Average); + + /*! + * Destroys this instance of the File. + */ + virtual ~File(); + + /*! + * Returns the XiphComment for this file. XiphComment implements the tag + * interface, so this serves as the reimplementation of + * TagLib::File::tag(). + */ + virtual Ogg::XiphComment *tag() const; + + /*! + * Returns the Opus::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + virtual Properties *audioProperties() const; + + virtual bool save(); + + private: + File(const File &); + File &operator=(const File &); + + void read(bool readProperties, Properties::ReadStyle propertiesStyle); + + class FilePrivate; + FilePrivate *d; + }; + } + } +} + +#endif diff --git a/taglib/ogg/opus/opusproperties.cpp b/taglib/ogg/opus/opusproperties.cpp new file mode 100644 index 00000000..70679d4c --- /dev/null +++ b/taglib/ogg/opus/opusproperties.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include +#include + +#include + +#include "opusproperties.h" +#include "opusfile.h" + +using namespace TagLib; +using namespace TagLib::Ogg; + +class Opus::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate(File *f, ReadStyle s) : + file(f), + style(s), + length(0), + inputSampleRate(0), + channels(0), + opusVersion(0) {} + + File *file; + ReadStyle style; + int length; + int inputSampleRate; + int channels; + int opusVersion; +}; + +//////////////////////////////////////////////////////////////////////////////// +// public members +//////////////////////////////////////////////////////////////////////////////// + +Opus::Properties::Properties(File *file, ReadStyle style) : AudioProperties(style) +{ + d = new PropertiesPrivate(file, style); + read(); +} + +Opus::Properties::~Properties() +{ + delete d; +} + +int Opus::Properties::length() const +{ + return d->length; +} + +int Opus::Properties::bitrate() const +{ + return 0; +} + +int Opus::Properties::sampleRate() const +{ + // Opus can decode any stream at a sample rate of 8, 12, 16, 24, or 48 kHz, + // so there is no single sample rate. Let's assume it's the highest + // possible. + return 48000; +} + +int Opus::Properties::channels() const +{ + return d->channels; +} + +int Opus::Properties::inputSampleRate() const +{ + return d->inputSampleRate; +} + +int Opus::Properties::opusVersion() const +{ + return d->opusVersion; +} + +//////////////////////////////////////////////////////////////////////////////// +// private members +//////////////////////////////////////////////////////////////////////////////// + +void Opus::Properties::read() +{ + // Get the identification header from the Ogg implementation. + + // http://tools.ietf.org/html/draft-terriberry-oggopus-01#section-5.1 + + ByteVector data = d->file->packet(0); + + // *Magic Signature* + int pos = 8; + + // *Version* (8 bits, unsigned) + d->opusVersion = uchar(data.at(pos)); + pos += 1; + + // *Output Channel Count* 'C' (8 bits, unsigned) + d->channels = uchar(data.at(pos)); + pos += 1; + + // *Pre-skip* (16 bits, unsigned, little endian) + ushort preSkip = data.mid(pos, 2).toUShort(false); + pos += 2; + + // *Input Sample Rate* (32 bits, unsigned, little endian) + d->inputSampleRate = data.mid(pos, 4).toUInt(false); + pos += 4; + + // *Output Gain* (16 bits, signed, little endian) + pos += 2; + + // *Channel Mapping Family* (8 bits, unsigned) + pos += 1; + + const Ogg::PageHeader *first = d->file->firstPageHeader(); + const Ogg::PageHeader *last = d->file->lastPageHeader(); + + if(first && last) { + long long start = first->absoluteGranularPosition(); + long long end = last->absoluteGranularPosition(); + + if(start >= 0 && end >= 0) + d->length = (int) ((end - start - preSkip) / 48000); + else { + debug("Opus::Properties::read() -- The PCM values for the start or " + "end of this file was incorrect."); + } + } + else + debug("Opus::Properties::read() -- Could not find valid first and last Ogg pages."); +} diff --git a/taglib/ogg/opus/opusproperties.h b/taglib/ogg/opus/opusproperties.h new file mode 100644 index 00000000..946f1675 --- /dev/null +++ b/taglib/ogg/opus/opusproperties.h @@ -0,0 +1,96 @@ +/*************************************************************************** + copyright : (C) 2012 by Lukáš Lalinský + email : lalinsky@gmail.com + + copyright : (C) 2002 - 2008 by Scott Wheeler + email : wheeler@kde.org + (original Vorbis implementation) +***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_OPUSPROPERTIES_H +#define TAGLIB_OPUSPROPERTIES_H + +#include "audioproperties.h" + +namespace TagLib { + + namespace Ogg { + + namespace Opus { + + class File; + + //! An implementation of audio property reading for Ogg Opus + + /*! + * This reads the data from an Ogg Opus stream found in the AudioProperties + * API. + */ + + class TAGLIB_EXPORT Properties : public AudioProperties + { + public: + /*! + * Create an instance of Opus::Properties with the data read from the + * Opus::File \a file. + */ + Properties(File *file, ReadStyle style = Average); + + /*! + * Destroys this Opus::Properties instance. + */ + virtual ~Properties(); + + // Reimplementations. + + virtual int length() const; + virtual int bitrate() const; + virtual int sampleRate() const; + virtual int channels() const; + + /*! + * The Opus codec supports decoding at multiple sample rates, there is no + * single sample rate of the encoded stream. This returns the sample rate + * of the original audio stream. + */ + int inputSampleRate() const; + + /*! + * Returns the Opus version, currently "0" (as specified by the spec). + */ + int opusVersion() const; + + private: + Properties(const Properties &); + Properties &operator=(const Properties &); + + void read(); + + class PropertiesPrivate; + PropertiesPrivate *d; + }; + } + } +} + +#endif diff --git a/taglib/toolkit/tfile.cpp b/taglib/toolkit/tfile.cpp index d0a6116f..8d7ccdc9 100644 --- a/taglib/toolkit/tfile.cpp +++ b/taglib/toolkit/tfile.cpp @@ -60,6 +60,7 @@ #include "mp4file.h" #include "wavpackfile.h" #include "speexfile.h" +#include "opusfile.h" #include "trueaudiofile.h" #include "aifffile.h" #include "wavfile.h" @@ -135,6 +136,8 @@ 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(); if(dynamic_cast(this)) return dynamic_cast(this)->properties(); if(dynamic_cast(this)) @@ -174,6 +177,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 if(dynamic_cast(this)) dynamic_cast(this)->removeUnsupportedProperties(properties); else if(dynamic_cast(this)) @@ -210,6 +215,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 if(dynamic_cast(this)) return dynamic_cast(this)->setProperties(properties); else if(dynamic_cast(this)) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f51e7bfa..4e428af8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,8 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/vorbis ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/flac + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/speex + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ogg/opus ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/flac ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/wavpack ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/mod @@ -60,6 +62,7 @@ SET(test_runner_SRCS test_it.cpp test_xm.cpp test_mpc.cpp + test_opus.cpp ) INCLUDE_DIRECTORIES(${CPPUNIT_INCLUDE_DIR}) diff --git a/tests/data/correctness_gain_silent_output.opus b/tests/data/correctness_gain_silent_output.opus new file mode 100644 index 0000000000000000000000000000000000000000..00972c42f64f285610c17ab548ac0bfa6960bdff GIT binary patch literal 35506 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 literal 0 HcmV?d00001 diff --git a/tests/test_opus.cpp b/tests/test_opus.cpp new file mode 100644 index 00000000..35ee3b3d --- /dev/null +++ b/tests/test_opus.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestOpus : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestOpus); + CPPUNIT_TEST(testProperties); + CPPUNIT_TEST(testReadComments); + CPPUNIT_TEST(testWriteComments); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testProperties() + { + Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT_EQUAL(7, f.audioProperties()->length()); + CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(1, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(48000, ((Ogg::Opus::Properties *)f.audioProperties())->inputSampleRate()); + } + + void testReadComments() + { + Ogg::Opus::File f(TEST_FILE_PATH_C("correctness_gain_silent_output.opus")); + CPPUNIT_ASSERT_EQUAL(StringList("Xiph.Org Opus testvectormaker"), f.tag()->fieldListMap()["ENCODER"]); + CPPUNIT_ASSERT(f.tag()->fieldListMap().contains("TESTDESCRIPTION")); + CPPUNIT_ASSERT(!f.tag()->fieldListMap().contains("ARTIST")); + CPPUNIT_ASSERT_EQUAL(String("libopus 0.9.11-66-g64c2dd7"), f.tag()->vendorID()); + } + + void testWriteComments() + { + ScopedFileCopy copy("correctness_gain_silent_output", ".opus"); + string filename = copy.fileName(); + + Ogg::Opus::File *f = new Ogg::Opus::File(filename.c_str()); + f->tag()->setArtist("Your Tester"); + f->save(); + delete f; + + f = new Ogg::Opus::File(filename.c_str()); + CPPUNIT_ASSERT_EQUAL(StringList("Xiph.Org Opus testvectormaker"), f->tag()->fieldListMap()["ENCODER"]); + CPPUNIT_ASSERT(f->tag()->fieldListMap().contains("TESTDESCRIPTION")); + CPPUNIT_ASSERT_EQUAL(StringList("Your Tester"), f->tag()->fieldListMap()["ARTIST"]); + CPPUNIT_ASSERT_EQUAL(String("libopus 0.9.11-66-g64c2dd7"), f->tag()->vendorID()); + delete f; + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestOpus);