From 648f5e588209464702e4955de614e391d3768ec8 Mon Sep 17 00:00:00 2001 From: Stephen Booth Date: Mon, 30 Dec 2024 07:23:11 -0600 Subject: [PATCH] Add Shorten (SHN) support (#1257) * Add Shorten (SHN) support * Add `` include and use `std::log2` * Use `uintptr_t` for buffer size calculations * Work around `byteSwap` not using fixed width types * Remove four-character codes * Attempt to fix `static_assert` * Revert previous commit * Update `read_uint`* functions * Use ByteVector for byte swaps * Use different ByteVector ctor * Rework variable-length input to use ByteVector * Rename some variables * Naming and formatting cleanup * Add basic Shorten tests * Rename a constant * Rename `internalFileType` to `fileType` * Add documentation on `fileType` meaning * Add DO_NOT_DOCUMENT guard * Fix shadowVariable issues reported by cppcheck cppcheck --enable=all --inline-suppr \ --suppress=noExplicitConstructor --suppress=unusedFunction \ --suppress=missingIncludeSystem --project=compile_commands.json * Formatting cleanup * More explicit types Reason for these changes: getRiceGolombCode(k, uInt32CodeSize) was called with int k for uint32_t& argument. There was also a warning from MSVC for line 299: warning C4267: 'argument': conversion from 'size_t' to 'int' * Additional explicit types * Rename `SHN` namespace to `Shorten` Also rename files to match --------- Co-authored-by: Urs Fleisch --- bindings/c/CMakeLists.txt | 1 + bindings/c/tag_c.cpp | 4 + bindings/c/tag_c.h | 3 +- taglib/CMakeLists.txt | 12 +- taglib/fileref.cpp | 6 + taglib/shorten/shortenfile.cpp | 542 +++++++++++++++++++++++++++ taglib/shorten/shortenfile.h | 140 +++++++ taglib/shorten/shortenproperties.cpp | 112 ++++++ taglib/shorten/shortenproperties.h | 74 ++++ taglib/shorten/shortentag.cpp | 114 ++++++ taglib/shorten/shortentag.h | 139 +++++++ taglib/shorten/shortenutils.h | 51 +++ tests/CMakeLists.txt | 2 + tests/data/2sec-silence.shn | 12 + tests/test_fileref.cpp | 1 + tests/test_shorten.cpp | 44 +++ tests/test_sizes.cpp | 6 + 17 files changed, 1261 insertions(+), 2 deletions(-) create mode 100644 taglib/shorten/shortenfile.cpp create mode 100644 taglib/shorten/shortenfile.h create mode 100644 taglib/shorten/shortenproperties.cpp create mode 100644 taglib/shorten/shortenproperties.h create mode 100644 taglib/shorten/shortentag.cpp create mode 100644 taglib/shorten/shortentag.h create mode 100644 taglib/shorten/shortenutils.h create mode 100644 tests/data/2sec-silence.shn create mode 100644 tests/test_shorten.cpp diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 1bd75636..c97841c4 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -25,6 +25,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/opus ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsf ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsdiff + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/shorten ) set(tag_c_HDRS tag_c.h) diff --git a/bindings/c/tag_c.cpp b/bindings/c/tag_c.cpp index b90d17f5..10b749a4 100644 --- a/bindings/c/tag_c.cpp +++ b/bindings/c/tag_c.cpp @@ -55,6 +55,7 @@ #include "opusfile.h" #include "dsffile.h" #include "dsdifffile.h" +#include "shortenfile.h" #include "tag.h" #include "id3v2framefactory.h" @@ -190,6 +191,9 @@ TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type) case TagLib_File_DSDIFF: file = new DSDIFF::File(filename); break; + case TagLib_File_SHORTEN: + file = new Shorten::File(filename); + break; default: break; } diff --git a/bindings/c/tag_c.h b/bindings/c/tag_c.h index f3f4e315..fc6b7acc 100644 --- a/bindings/c/tag_c.h +++ b/bindings/c/tag_c.h @@ -130,7 +130,8 @@ typedef enum { TagLib_File_XM, TagLib_File_Opus, TagLib_File_DSF, - TagLib_File_DSDIFF + TagLib_File_DSDIFF, + TagLib_File_SHORTEN } TagLib_File_Type; /*! diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index c6785fb8..aa6cf327 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -26,6 +26,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/xm ${CMAKE_CURRENT_SOURCE_DIR}/dsf ${CMAKE_CURRENT_SOURCE_DIR}/dsdiff + ${CMAKE_CURRENT_SOURCE_DIR}/shorten ) set(tag_HDRS @@ -141,6 +142,9 @@ set(tag_HDRS dsdiff/dsdifffile.h dsdiff/dsdiffproperties.h dsdiff/dsdiffdiintag.h + shorten/shortenfile.h + shorten/shortenproperties.h + shorten/shortentag.h ) set(mpeg_SRCS @@ -308,6 +312,12 @@ set(dsdiff_SRCS dsdiff/dsdiffdiintag.cpp ) +set(shorten_SRCS + shorten/shortenfile.cpp + shorten/shortenproperties.cpp + shorten/shortentag.cpp +) + set(toolkit_SRCS toolkit/tstring.cpp toolkit/tstringlist.cpp @@ -331,7 +341,7 @@ set(tag_LIB_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} ${opus_SRCS} - ${dsf_SRCS} ${dsdiff_SRCS} + ${dsf_SRCS} ${dsdiff_SRCS} ${shorten_SRCS} tag.cpp tagunion.cpp fileref.cpp diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index c59b65f3..95cc1a3e 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -57,6 +57,7 @@ #include "xmfile.h" #include "dsffile.h" #include "dsdifffile.h" +#include "shortenfile.h" using namespace TagLib; @@ -180,6 +181,8 @@ namespace file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle); else if(ext == "DFF" || ext == "DSDIFF") file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(ext == "SHN") + file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle); // if file is not valid, leave it to content-based detection. @@ -231,6 +234,8 @@ namespace file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle); else if(DSDIFF::File::isSupported(stream)) file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(Shorten::File::isSupported(stream)) + file = new Shorten::File(stream, readAudioProperties, audioPropertiesStyle); // isSupported() only does a quick check, so double check the file here. @@ -436,6 +441,7 @@ StringList FileRef::defaultFileExtensions() l.append("dsf"); l.append("dff"); l.append("dsdiff"); // alias for "dff" + l.append("shn"); return l; } diff --git a/taglib/shorten/shortenfile.cpp b/taglib/shorten/shortenfile.cpp new file mode 100644 index 00000000..850708e4 --- /dev/null +++ b/taglib/shorten/shortenfile.cpp @@ -0,0 +1,542 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "shortenfile.h" + +#include + +#include "shortenutils.h" + +#include "tdebug.h" +#include "tutils.h" +#include "tagutils.h" +#include "tpropertymap.h" + +using namespace TagLib; + +namespace { + +// MARK: Constants + + constexpr int minSupportedVersion = 1; + constexpr int maxSupportedVersion = 3; + + // Possible values of k + constexpr int32_t channelCountCodeSize = 0; + constexpr int32_t functionCodeSize = 2; + constexpr int32_t verbatimChunkSizeCodeSize = 5; + constexpr int32_t verbatimByteCodeSize = 8; + constexpr int32_t uInt32CodeSize = 2; + constexpr int32_t skipBytesCodeSize = 1; + constexpr int32_t lpcqCodeSize = 2; + constexpr int32_t extraByteCodeSize = 7; + constexpr int32_t fileTypeCodeSize = 4; + + constexpr int32_t functionVerbatim = 9; + + constexpr int32_t canonicalHeaderSize = 44; + constexpr int32_t verbatimChunkMaxSize = 256; + + constexpr uint32_t maxChannelCount = 8; + + constexpr uint32_t defaultBlockSize = 256; + constexpr uint32_t maxBlockSize = 65535; + + constexpr int waveFormatPCMTag = 0x0001; + +// MARK: Variable-Length Input + + //! Variable-length input using Golomb-Rice coding. + class VariableLengthInput { + public: + //! Creates a new \c VariableLengthInput object. + VariableLengthInput(File *file) : file(file) {} + + ~VariableLengthInput() = default; + + VariableLengthInput() = delete; + VariableLengthInput(const VariableLengthInput &) = delete; + VariableLengthInput(VariableLengthInput &&) = delete; + VariableLengthInput &operator=(const VariableLengthInput &) = delete; + VariableLengthInput &operator=(VariableLengthInput &&) = delete; + + bool getRiceGolombCode(int32_t &i32, int k); + bool getUInt(uint32_t &ui32, int version, int32_t k); + + private: + //! Refills \c bitBuffer with a single \c uint32_t from \c buffer, + //! refilling \c buffer if necessary. + bool refillBitBuffer(); + + //! Input stream + File *file { nullptr }; + //! Byte buffer + ByteVector buffer; + //! Current position in buffer + unsigned int bufferPosition { 0 }; + //! Bit buffer + uint32_t bitBuffer { 0 }; + //! Bits available in \c bitBuffer, 0..32 + int bitsAvailable { 0 }; + }; + + bool VariableLengthInput::getRiceGolombCode(int32_t &i32, int32_t k) + { + static constexpr uint32_t sMaskTable[] = { + 0x0, + 0x1, 0x3, 0x7, 0xf, + 0x1f, 0x3f, 0x7f, 0xff, + 0x1ff, 0x3ff, 0x7ff, 0xfff, + 0x1fff, 0x3fff, 0x7fff, 0xffff, + 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, + 0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, + 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff + }; + + if(bitsAvailable == 0 && !refillBitBuffer()) + return false; + + int32_t result; + for(result = 0; !(bitBuffer & (1L << --bitsAvailable)); ++result) { + if(bitsAvailable == 0 && !refillBitBuffer()) + return false; + } + + while(k != 0) { + if(bitsAvailable >= k) { + result = (result << k) | static_cast( + (bitBuffer >> (bitsAvailable - k)) & sMaskTable[k]); + bitsAvailable -= k; + k = 0; + } + else { + result = (result << bitsAvailable) | static_cast( + bitBuffer & sMaskTable[bitsAvailable]); + k -= bitsAvailable; + if(!refillBitBuffer()) + return false; + } + } + + i32 = result; + return true; + } + + bool VariableLengthInput::getUInt(uint32_t &ui32, int version, int32_t k) + { + if(version > 0 && !getRiceGolombCode(k, uInt32CodeSize)) + return false; + + int32_t i32; + if(!getRiceGolombCode(i32, k)) + return false; + ui32 = static_cast(i32); + return true; + } + + bool VariableLengthInput::refillBitBuffer() + { + if(buffer.size() - bufferPosition < 4) { + static constexpr size_t bufferSize = 512; + auto block = file->readBlock(bufferSize); + if(block.size() < 4) + return false; + buffer = block; + bufferPosition = 0; + } + + bitBuffer = buffer.toUInt(bufferPosition, true); + bufferPosition += 4; + bitsAvailable = 32; + + return true; + } +} // namespace + +class Shorten::File::FilePrivate +{ +public: + FilePrivate() = default; + ~FilePrivate() = default; + + FilePrivate(const FilePrivate &) = delete; + FilePrivate &operator=(const FilePrivate &) = delete; + + std::unique_ptr properties; + std::unique_ptr tag; +}; + +bool Shorten::File::isSupported(IOStream *stream) +{ + // A Shorten file has to start with "ajkg" + const ByteVector id = Utils::readHeader(stream, 4, false); + return id.startsWith("ajkg"); +} + +Shorten::File::File(FileName file, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + TagLib::File(file), + d(std::make_unique()) +{ + if(isOpen()) + read(propertiesStyle); +} + +Shorten::File::File(IOStream *stream, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + TagLib::File(stream), + d(std::make_unique()) +{ + if(isOpen()) + read(propertiesStyle); +} + +Shorten::File::~File() = default; + +Shorten::Tag *Shorten::File::tag() const +{ + return d->tag.get(); +} + +PropertyMap Shorten::File::properties() const +{ + return d->tag->properties(); +} + +PropertyMap Shorten::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + +Shorten::Properties *Shorten::File::audioProperties() const +{ + return d->properties.get(); +} + +bool Shorten::File::save() +{ + if(readOnly()) { + debug("Shorten::File::save() - Cannot save to a read only file."); + return false; + } + + debug("Shorten::File::save() - Saving not supported."); + return false; +} + +void Shorten::File::read(AudioProperties::ReadStyle propertiesStyle) +{ + if(!isOpen()) + return; + + // Read magic number + auto magic = readBlock(4); + if(magic != "ajkg") { + debug("Shorten::File::read() -- Not a Shorten file."); + setValid(false); + return; + } + + PropertyValues props{}; + + // Read file version + int version = readBlock(1).toUInt(); + if(version < minSupportedVersion || version > maxSupportedVersion) { + debug("Shorten::File::read() -- Unsupported version."); + setValid(false); + return; + } + props.version = version; + + // Set up variable length input + VariableLengthInput input(this); + + // Read file type + uint32_t fileType; + if(!input.getUInt(fileType, version, fileTypeCodeSize)) { + debug("Shorten::File::read() -- Unable to read file type."); + setValid(false); + return; + } + props.fileType = static_cast(fileType); + + // Read number of channels + uint32_t channelCount = 0; + if(!input.getUInt(channelCount, version, channelCountCodeSize) || + channelCount == 0 || channelCount > maxChannelCount) { + debug("Shorten::File::read() -- Invalid or unsupported channel count."); + setValid(false); + return; + } + props.channelCount = static_cast(channelCount); + + // Read block size if version > 0 + if(version > 0) { + uint32_t blockSize = 0; + if(!input.getUInt(blockSize, version, + static_cast(std::log2(defaultBlockSize))) || + blockSize == 0 || blockSize > maxBlockSize) { + debug("Shorten::File::read() -- Invalid or unsupported block size."); + setValid(false); + return; + } + + uint32_t maxnlpc = 0; + if(!input.getUInt(maxnlpc, version, lpcqCodeSize) /*|| maxnlpc > 1024*/) { + debug("Shorten::File::read() -- Invalid maximum nlpc."); + setValid(false); + return; + } + + uint32_t nmean = 0; + if(!input.getUInt(nmean, version, 0) /*|| nmean > 32768*/) { + debug("Shorten::File::read() -- Invalid nmean."); + setValid(false); + return; + } + + uint32_t skipCount; + if(!input.getUInt(skipCount, version, skipBytesCodeSize)) { + setValid(false); + return; + } + + for(uint32_t i = 0; i < skipCount; ++i) { + uint32_t dummy; + if(!input.getUInt(dummy, version, extraByteCodeSize)) { + setValid(false); + return; + } + } + } + + // Parse the WAVE or AIFF header in the verbatim section + + int32_t function; + if(!input.getRiceGolombCode(function, functionCodeSize) || + function != functionVerbatim) { + debug("Shorten::File::read() -- Missing initial verbatim section."); + setValid(false); + return; + } + + int32_t header_size; + if(!input.getRiceGolombCode(header_size, verbatimChunkSizeCodeSize) || + header_size < canonicalHeaderSize || header_size > verbatimChunkMaxSize) { + debug("Shorten::File::read() -- Incorrect header size."); + setValid(false); + return; + } + + ByteVector header(header_size, 0); + + auto it = header.begin(); + for(int32_t i = 0; i < header_size; ++i) { + int32_t byte; + if(!input.getRiceGolombCode(byte, verbatimByteCodeSize)) { + debug("Shorten::File::read() -- Unable to read header."); + setValid(false); + return; + } + + *it++ = static_cast(byte); + } + + // header is at least canonicalHeaderSize (44) bytes in size + + auto chunkID = header.toUInt(0, true); +// auto chunkSize = header.toUInt(4, true); + + const auto chunkData = ByteVector(header, 8, header.size() - 8); + + // WAVE + if(chunkID == 0x52494646 /*'RIFF'*/) { + unsigned int offset = 0; + + chunkID = chunkData.toUInt(offset, true); + offset += 4; + if(chunkID != 0x57415645 /*'WAVE'*/) { + debug("Shorten::File::read() -- Missing 'WAVE' in 'RIFF' chunk."); + setValid(false); + return; + } + + auto sawFormatChunk = false; + uint32_t dataChunkSize = 0; + uint16_t blockAlign = 0; + + while(offset < chunkData.size()) { + chunkID = chunkData.toUInt(offset, true); + offset += 4; + + auto chunkSize = chunkData.toUInt(offset, false); + offset += 4; + + switch(chunkID) { + case 0x666d7420 /*'fmt '*/: + { + if(chunkSize < 16) { + debug("Shorten::File::read() -- 'fmt ' chunk is too small."); + setValid(false); + return; + } + + int formatTag = chunkData.toUShort(offset, false); + offset += 2; + if(formatTag != waveFormatPCMTag) { + debug("Shorten::File::read() -- Unsupported WAVE format tag."); + setValid(false); + return; + } + + int fmtChannelCount = chunkData.toUShort(offset, false); + offset += 2; + if(props.channelCount != fmtChannelCount) + debug("Shorten::File::read() -- Channel count mismatch between Shorten and 'fmt ' chunk."); + + props.sampleRate = static_cast(chunkData.toUInt(offset, false)); + offset += 4; + + // Skip average bytes per second + offset += 4; + + blockAlign = chunkData.toUShort(offset, false); + offset += 2; + + props.bitsPerSample = static_cast(chunkData.toUShort(offset, false)); + offset += 2; + + sawFormatChunk = true; + + break; + } + + case 0x64617461 /*'data'*/: + dataChunkSize = chunkSize; + break; + } + } + + if(!sawFormatChunk) { + debug("Shorten::File::read() -- Missing 'fmt ' chunk."); + setValid(false); + return; + } + + if(dataChunkSize && blockAlign) + props.sampleFrames = static_cast(dataChunkSize / blockAlign); + } + // AIFF + else if(chunkID == 0x464f524d /*'FORM'*/) { + unsigned int offset = 0; + + chunkID = chunkData.toUInt(offset, true); + offset += 4; + if(chunkID != 0x41494646 /*'AIFF'*/ && chunkID != 0x41494643 /*'AIFC'*/) { + debug("Shorten::File::read() -- Missing 'AIFF' or 'AIFC' in 'FORM' chunk."); + setValid(false); + return; + } + +// if(chunkID == 0x41494643 /*'AIFC'*/) +// props.big_endian = true; + + auto sawCommonChunk = false; + while(offset < chunkData.size()) { + chunkID = chunkData.toUInt(offset, true); + offset += 4; + + auto chunkSize = chunkData.toUInt(offset, true); + offset += 4; + + // All chunks must have an even length but the pad byte is not included in chunkSize + chunkSize += (chunkSize & 1); + + switch(chunkID) { + case 0x434f4d4d /*'COMM'*/: + { + if(chunkSize < 18) { + debug("Shorten::File::read() -- 'COMM' chunk is too small."); + setValid(false); + return; + } + + int commChannelCount = chunkData.toUShort(offset, true); + offset += 2; + if(props.channelCount != commChannelCount) + debug("Shorten::File::read() -- Channel count mismatch between Shorten and 'COMM' chunk."); + + props.sampleFrames = static_cast(chunkData.toUInt(offset, true)); + offset += 4; + + props.bitsPerSample = static_cast(chunkData.toUShort(offset, true)); + offset += 2; + + // sample rate is IEEE 754 80-bit extended float + // (16-bit exponent, 1-bit integer part, 63-bit fraction) + auto exp = static_cast(chunkData.toUShort(offset, true)) - 16383 - 63; + offset += 2; + if(exp < -63 || exp > 63) { + debug("Shorten::File::read() -- exp out of range."); + setValid(false); + return; + } + + auto frac = chunkData.toULongLong(offset, true); + offset += 8; + if(exp >= 0) + props.sampleRate = static_cast(frac << exp); + else + props.sampleRate = static_cast( + (frac + (static_cast(1) << (-exp - 1))) >> -exp); + + sawCommonChunk = true; + + break; + } + + // Skip all other chunks + default: + offset += chunkSize; + break; + } + } + + if(!sawCommonChunk) { + debug("Shorten::File::read() -- Missing 'COMM' chunk"); + setValid(false); + return; + } + } + else { + debug("Shorten::File::read() -- Unsupported data format."); + setValid(false); + return; + } + + d->tag = std::make_unique(); + d->properties = std::make_unique(&props, propertiesStyle); +} diff --git a/taglib/shorten/shortenfile.h b/taglib/shorten/shortenfile.h new file mode 100644 index 00000000..96359808 --- /dev/null +++ b/taglib/shorten/shortenfile.h @@ -0,0 +1,140 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * 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_SHORTENFILE_H +#define TAGLIB_SHORTENFILE_H + +#include + +#include "taglib_export.h" +#include "tfile.h" + +#include "shortenproperties.h" +#include "shortentag.h" + +namespace TagLib { + + //! An implementation of Shorten metadata + + /*! + * This is an implementation of Shorten metadata. + */ + + namespace Shorten { + + //! An implementation of \c TagLib::File with Shorten specific methods + + /*! + * This implements and provides an interface for Shorten files to the + * \c TagLib::Tag and \c TagLib::AudioProperties interfaces by way of implementing + * the abstract \c TagLib::File API as well as providing some additional + * information specific to Shorten files. + */ + + class TAGLIB_EXPORT File : public TagLib::File { + public: + /*! + * Constructs a Shorten file from \a file. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + */ + File(FileName file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Constructs a Shorten file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \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, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + ~File() override; + + File(const File &) = delete; + File &operator=(const File &) = delete; + + /*! + * Returns the \c Shorten::Tag for this file. + * + * \note While the returned \c Tag instance is non-null Shorten tags are not supported. + */ + Tag *tag() const override; + + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const override; + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &) override; + + /*! + * Returns the \c Shorten::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + Properties *audioProperties() const override; + + /*! + * Save the file. + * + * \note Saving Shorten tags is not supported. + */ + bool save() override; + + /*! + * Returns whether or not the given \a stream can be opened as a Shorten + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + + private: + void read(AudioProperties::ReadStyle propertiesStyle); + + class FilePrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + } // namespace Shorten +} // namespace TagLib + +#endif diff --git a/taglib/shorten/shortenproperties.cpp b/taglib/shorten/shortenproperties.cpp new file mode 100644 index 00000000..65aca412 --- /dev/null +++ b/taglib/shorten/shortenproperties.cpp @@ -0,0 +1,112 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "shortenproperties.h" + +#include "shortenutils.h" + +using namespace TagLib; + +class Shorten::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() = default; + ~PropertiesPrivate() = default; + + PropertiesPrivate(const PropertiesPrivate &) = delete; + PropertiesPrivate &operator=(const PropertiesPrivate &) = delete; + + int version { 0 }; + int fileType { 0 }; + int channelCount { 0 }; + int sampleRate { 0 }; + int bitsPerSample { 0 }; + unsigned long sampleFrames { 0 }; + + // Computed + int bitrate { 0 }; + int length { 0 }; +}; + +Shorten::Properties::Properties(const PropertyValues *values, ReadStyle style) : + AudioProperties(style), + d(std::make_unique()) +{ + if(values) { + d->version = values->version; + d->fileType = values->fileType; + d->channelCount = values->channelCount; + d->sampleRate = values->sampleRate; + d->bitsPerSample = values->bitsPerSample; + d->sampleFrames = values->sampleFrames; + + d->bitrate = static_cast(d->sampleRate * d->bitsPerSample * d->channelCount / 1000.0 + 0.5); + if(d->sampleRate > 0) + d->length = static_cast(d->sampleFrames * 1000.0 / d->sampleRate + 0.5); + } +} + +Shorten::Properties::~Properties() = default; + +int Shorten::Properties::lengthInMilliseconds() const +{ + return d->length; +} + +int Shorten::Properties::bitrate() const +{ + return d->bitrate; +} + +int Shorten::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int Shorten::Properties::channels() const +{ + return d->channelCount; +} + +int Shorten::Properties::shortenVersion() const +{ + return d->version; +} + +int Shorten::Properties::fileType() const +{ + return d->fileType; +} + +int Shorten::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +unsigned long Shorten::Properties::sampleFrames() const +{ + return d->sampleFrames; +} diff --git a/taglib/shorten/shortenproperties.h b/taglib/shorten/shortenproperties.h new file mode 100644 index 00000000..b6a389c7 --- /dev/null +++ b/taglib/shorten/shortenproperties.h @@ -0,0 +1,74 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * 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_SHORTENPROPERTIES_H +#define TAGLIB_SHORTENPROPERTIES_H + +#include + +#include "taglib_export.h" +#include "audioproperties.h" + +namespace TagLib { + namespace Shorten { + + struct PropertyValues; + + //! An implementation of audio properties for Shorten + class TAGLIB_EXPORT Properties : public AudioProperties { + public: + Properties(const PropertyValues *values, ReadStyle style = Average); + ~Properties() override; + + Properties(const Properties &) = delete; + Properties &operator=(const Properties &) = delete; + + int lengthInMilliseconds() const override; + int bitrate() const override; + int sampleRate() const override; + int channels() const override; + + //! Returns the Shorten file version (1-3). + int shortenVersion() const; + //! Returns the file type (0-9). + //! 0 = 8-bit µ-law, + //! 1 = signed 8-bit PCM, 2 = unsigned 8-bit PCM, + //! 3 = signed big-endian 16-bit PCM, 4 = unsigned big-endian 16-bit PCM, + //! 5 = signed little-endian 16-bit PCM, 6 = unsigned little-endian 16-bit PCM, + //! 7 = 8-bit ITU-T G.711 µ-law, 8 = 8-bit µ-law, + //! 9 = 8-bit A-law, 10 = 8-bit ITU-T G.711 A-law + int fileType() const; + int bitsPerSample() const; + unsigned long sampleFrames() const; + + private: + class PropertiesPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + } // namespace Shorten +} // namespace TagLib + +#endif diff --git a/taglib/shorten/shortentag.cpp b/taglib/shorten/shortentag.cpp new file mode 100644 index 00000000..a7224810 --- /dev/null +++ b/taglib/shorten/shortentag.cpp @@ -0,0 +1,114 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * 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 "shortentag.h" + +#include "tpropertymap.h" + +using namespace TagLib; + +class Shorten::Tag::TagPrivate +{ +}; + +Shorten::Tag::Tag() : + d(std::make_unique()) +{ +} + +Shorten::Tag::~Tag() = default; + +String Shorten::Tag::title() const +{ + return String(); +} + +String Shorten::Tag::artist() const +{ + return String(); +} + +String Shorten::Tag::album() const +{ + return String(); +} + +String Shorten::Tag::comment() const +{ + return String(); +} + +String Shorten::Tag::genre() const +{ + return String(); +} + +unsigned int Shorten::Tag::year() const +{ + return 0; +} + +unsigned int Shorten::Tag::track() const +{ + return 0; +} + +void Shorten::Tag::setTitle(const String &) +{ +} + +void Shorten::Tag::setArtist(const String &) +{ +} + +void Shorten::Tag::setAlbum(const String &) +{ +} + +void Shorten::Tag::setComment(const String &) +{ +} + +void Shorten::Tag::setGenre(const String &) +{ +} + +void Shorten::Tag::setYear(unsigned int) +{ +} + +void Shorten::Tag::setTrack(unsigned int) +{ +} + +PropertyMap Shorten::Tag::properties() const +{ + return PropertyMap{}; +} + +PropertyMap Shorten::Tag::setProperties(const PropertyMap &origProps) +{ + return PropertyMap{origProps}; +} diff --git a/taglib/shorten/shortentag.h b/taglib/shorten/shortentag.h new file mode 100644 index 00000000..b4f394a1 --- /dev/null +++ b/taglib/shorten/shortentag.h @@ -0,0 +1,139 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * 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_SHORTENTAG_H +#define TAGLIB_SHORTENTAG_H + +#include "tag.h" + +namespace TagLib { + namespace Shorten { + + //! A Shorten file tag implementation + + /*! + * Tags for Shorten files. + * + * This is a stub class; Shorten files do not support tags. + */ + class TAGLIB_EXPORT Tag : public TagLib::Tag + { + public: + Tag(); + ~Tag() override; + + Tag(const Tag &) = delete; + Tag &operator=(const Tag &) = delete; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String title() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String artist() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String album() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String comment() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String genre() const override; + + /*! + * Not supported by Shorten files. Therefore always returns 0. + */ + unsigned int year() const override; + + /*! + * Not supported by Shorten files. Therefore always returns 0. + */ + unsigned int track() const override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setTitle(const String &title) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setArtist(const String &artist) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setAlbum(const String &album) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setComment(const String &comment) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setGenre(const String &genre) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setYear(unsigned int year) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setTrack(unsigned int track) override; + + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const override; + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &) override; + + private: + class TagPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + + } // namespace Shorten +} // namespace TagLib + +#endif diff --git a/taglib/shorten/shortenutils.h b/taglib/shorten/shortenutils.h new file mode 100644 index 00000000..bc9c24bd --- /dev/null +++ b/taglib/shorten/shortenutils.h @@ -0,0 +1,51 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * 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_SHORTENUTILS_H +#define TAGLIB_SHORTENUTILS_H + +// THIS FILE IS NOT A PART OF THE TAGLIB API + +#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header + +namespace TagLib { + namespace Shorten { + + /// Values shared with \c Shorten::Properties by \c Shorten::File + struct PropertyValues + { + int version { 0 }; + int fileType { 0 }; + int channelCount { 0 }; + int sampleRate { 0 }; + int bitsPerSample { 0 }; + unsigned long sampleFrames { 0 }; + }; + } // namespace Shorten +} // namespace TagLib + +#endif + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae0d9e46..b36f75d8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/xm ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsf ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsdiff + ${CMAKE_CURRENT_SOURCE_DIR}/../taglib/shorten ) SET(test_runner_SRCS @@ -74,6 +75,7 @@ SET(test_runner_SRCS test_speex.cpp test_dsf.cpp test_dsdiff.cpp + test_shorten.cpp test_sizes.cpp test_versionnumber.cpp ) diff --git a/tests/data/2sec-silence.shn b/tests/data/2sec-silence.shn new file mode 100644 index 00000000..58fa811e --- /dev/null +++ b/tests/data/2sec-silence.shn @@ -0,0 +1,12 @@ +ajkgp %YRѨKWըkmA@  +D@  +D K A`*UUUVʪT]UWTUUURUj֪ut꺩WU*uuUUVUJkR*\RꪮUU.uuRWU.UUkJTUuUUUUUU]WUJR*UU]UUU]JuU˪UuuUrtJTrUjU+RWR].U)UUR\VUjURꫩRWU*WU*R]JWTtVZꪪZR*ҪrWUjUKZw*UW*VUʺWW*UUWUT\]TZR5UTU.U]U.UUW+UUUUUuUUUUZ]RUURUrUU+U*W+UUKU-UTWJUUU.]+W*]URUJUUtjUURꮺrkVUUUUV]RUUUrZEUUr*\Z\RZUUuUVjuRU]TJU]KU*ֺuWR*RꫥZUvUUZ媫RUuWUUVRꪕUUJJUUT.UURꪪծTU]R5˖R*]TUJUʮUjUUUUUʭWUt]J\TuZUVZZRUժ]UUU]URUKUUUUVZKU\k˗+URUUUTUVuR.U\UUUUR.].UKuu*UUkUUUU]UJUUuUUUu+WJR]U*DWZU*]UUKUJWTUuUKUj]*ꪕUUrԪֺUWURVUTUU.ZWUU!RuUԺZ]-UUTtkUUUK\UuRUU-uU-U*\UU]rRuUk\"*U*ڪuK]TTUUUK]UUʗUUTk]KUTuRUJt"ZUrUj]URUUUUUJUZJUVW*URkUU]U+UUuUJUuT]uuW*UuUUU"UVUꫥUUUU*uUrU*uUU]+UԪTҺu*UUꪪVU]HUuURUKZ]UUUKUUTuTjW*]+UUU.ZJ]T*U+UuUtUUUҥ\uW]WRWU\Uu.Rj宪U\UuUZUUjUUKU*BTUUuJ֭UJUU\jU*]Ujj].WUUUUUUUuUJ#UkUVJkUKUUUKZU*ZUU]W*ܺUUUUҺUWZHU]JuRRUJUJ\*UuKV\UTUVU.uURuJBUTRU*UUUUU]UJU].UUUTUUUUUUUrꪥkUJu+RJUjHUU]UU]UUuk]U*U.\jUWUkURKZw*UUKUUUTUKZꮕu*U*uHKUrUU]UuTuu*TUuUUWUUukURTUUu*u)tUKUԪ]RUTWUuJUuJWuUZRUҮjrVUJUKU]UUKZK]JWR*U\UUWUUU]*]JuUUrUUZUuKUUURURUWJEUUuWUUZuUUUUUuVUUꪩ]UU.VWUTuWT]R]u*UTTUuUUU**U]W*UUUURU.J֫UuUrtvjrUURVҩV]V]jUUUUuJ.kUVUuUU+UUUUUTRuZUҫUu]U.RUrRU\UUuUԪUU-uUꪩWVuW]RꪪTUUJUV˪U*WRU]UUKkBukUWUUJrꪫUJUUTUR֮]T]UJUURURUUkJTUU]UTjUUr]RꪪWU*UUUU-u]UUURutuU*tȪuUTjUUTUUWUUJTuUkUU.UUUUUjuU)JUUKUu]UUUUJ]]JjJR]uԪUJuUuWJUTURkt\UWURUuWUU*RTWUTJU]UJURUJTZu.ˮ].jVZU.\]RWTu]K*u*UuuU)TUJU\U]rTUuVꪑRRuUU]U*UUuUZUUWUW\UUUVUUU]*ԮꪪꪕUUjUTuUUjERUU.UUUZ]RRTUVRUUUTUJZꫩRꪺU*UWKUUTVҪUUkZ]UJRWURUWUUUUUKURU*.Juj\KTrꪪV]RkUUURTUUҪrU]JJUR*UUJꪥ\VTUJUUWUUTuUj.]RժTuU*ZkT]*UK\UKUUUZJꫪURꪪ]rUUJJUUUU-uU*UUKTTBJU]UVjUUUTUUJUUZԪUUWRU*UʫUUTT*UT]UWRrJ]UUU*UZUUUʪUUkW*+UJUj]KRWUR֪Uu\UU +TWK]Uʪj\u*UVUWUu*UUUꪩVVrUTVUTꪪԪ.U*WUuTVR]JUUUUҵU-R*UUUUUu]ZR*rRUUUUZJZU.U]*UuZUJUUJUW]TTZURZUtUUUUUWTUR꺪uURUUtUUKUU]TU֮TUK]UUJU*UTUU]UU"URuU*UUT\].]*u]UUTU]JkR]UҪ"]UJuUUUʹU]KU*UUUjTJU]U].UUU˕uR몥U.U!UUu-Zꪕ*URꪩrUUUJVUUTukUukTRj֪uUU*W-jUTUUUUU*TKRUuUUKkUUkUUJʩURR]jURw*UUWUrUUJHuUURRԮUV]R]UUUjR]TWKUJUjҵZr.]K]H\TWURTUUUU]juUUj]RUUJꮪUtUUU\UUuU\UUZꔊUrTҵUUrUJҫ]UrUk]VTW]WUUJUU]Rj-UJU)UjꪩUUr\T]UUժUʪTUUU\U]UTUJRꪺRZUtUTJʥ]JꮕUT֪UUuTUUUR]]UU]UJUUUurUUUTU*UUUZ]\EU*UJuRJUKZꪕUUuUrԺU˪RUUWZUR]UUUJU*UU"UUV]RUtUUVjT\]UUTUT]U*VWT몪UUHꪪuUUjRUUUUUVrZUUUUUjUUUJʪꪪkj#UkR]TuUUUJuUUUUUT]RUUTUUUJWU֪WR\k\URUURKWR]jJꫥԮ.j]UUUW*UUUUTUU-TUTUuUuU*rUUU*UUZ\URUJjUWUU*UTUT*]UU]uRUU\WVU\rꪪUUTURUUrU*ժUUUꪪ媥U*ժ]TK֫UU*RjTUWUUJԪVֺZUU*ԵjkUU*ZUUUU\k]UUUJU]U]*UuUJVW-UUUUUUVժjU+rRU֪ꪵuW.UUTURuuUU]URHR֪uJURVWU*WUj\UkTUkjUUUUUKUWKWHWUjWuT]\UU\\\UWVTu*UJꫩUU]]UJW.UrrWHu+trU*]*R]tuU*U*UW-j]]URU+ZHU.TUWUUUuURԵUJUJUVurUU*URTJuUUJꖪDRꪗZUUU.]UUʺR꺪UUTWRWUjUU.U.UUUZWTH*UJURWUUUVuKTT*ʪZUZRVҪꮪUU]-wHUU.WtURꫪWjuJ]U*UkuWUZUR]RTUURuKW*RUUU!URUjUU*UKuU.UVWUR]UUWRJTuT]UUUU*U+WU*UUUTUWU]U]\Zu.WUUWU*URUUWV\W.WJZUUURJUUV*].UU]*ꮪҪU].U*uUUUK*UUR]]*RU]W.U+WUZjHUWJRTTUUUUUUK\U]TJ媪UUu]UU*juUUR*W]UWU"֪.W*]UW.uU]*UʪTU*r]UUT]TW*TJ-WVuKjUUUKRUU+U+ZZUUUUUZjuTU#RUUZUU*W*UU֪UUUVuUUuWUwUJꪕժj*ҪUUUKruR%utꪪZ*WUUUUUJU.JUUUUWU.UvʪjUtUkꮪ.ծUZ*\kUUVURUr\ZURtURjURURU]KT˕k]UWUUUKUUUUUtUZr*]RUU**tҪVUUURUURUKTUtRV].UjWUUUUUKWtUT]J\HkV*]TUKUUt]UU.jVUr֪u.ZuUv.)Tr\UUW-RWVuU]UUU*Uuj]]TWU]K]*UWUuTWJ]UU]UZHUUUUWUUUUTʺVrUTZTUJ*U*UuUUUURժuZUWU)uV֪]Jֵ\UU]UKUUuKVԮURZTKKTUU]TJTUUWRꮩUKUTTUR\u.UUUUUUU*T+RUU]JʪUU]U*UUZʪUUUUU]VURUr]RUW\JիU*j*UU*Urtu*UURTUuUUVZUTUuUUԪUUUJU]UWUUU*UU#U*UUU֪kUUUr꺪UUU]*].WK]UUU*UUUjuREu.UJTUUtUUKW*uUJUUVUJUUu*]U+-UUrtUJtUJZUuUUUUURUURZꮪ]UKUU]UUʪUUUKJ*WUUUKUr!uK\]JJuUUUU*jkrv꺫\UjZUUUR]K]kUWWU"]U*w)UvTRꪫUUTuUUZUUUKUVUKUUuUUUUꪪuuUUUU\HUU]U\U]jZTWJUJUUrJUTUUUWU.UTUTJ]]TT꫕WTUVuR]U*RVuTWTUUZU]ʪժZʪUjKHUUUVUZTUUUkWTUUU]U.]Z]RUrURuUU.*U˕U*jJ]+UJu*UUrUTUUW*UUZ]\UJUUrRꮩUuV +UUurZUZJUrU˪]uUU꺗UTUU*uuJU*JʥtթUZʫUUUUJ].u+UVUU*UUKU*RU].JUWUKTRZ]WUT\URuTUZ.UVUrURꪪ]RUUUUuJUU]UUT]]jUVUjUUU]UJKk]kUҪZ꫖WRu*UVRUWR%ҪUUJUU*UZ]ҩuRjUUUZꮥjRUUJWRʪʪT]*T*֥UU)UTWUUUUjTUUURUUuKuWUU]*Z]WUJʩUUJU]+KUUH]VKTUW.UUU.UʫUUҮJrURU*UtU+UU]KTUUU]WUU]j]TUZUtꪪRUU]UZUUjʥZꪪUZJVWuUUJJUU*uUTrUUuUURRԪUUU+Z+]UꪩUKu]UUUjTEjU*UTԺKRZ]R]JTuTtrUTUJrUUUHJZUVuJuUuUUUJURuUKUU]UV*UUUUWVꪥUJ]UUUR +WUtUu]-TUZԪJZꪪUUU+Z].JW+URꪮVjJjҵժګ˥]-U]TJjrVUU*]ru*rRU*ꪪUꪮWUVTU*TWTꪪꪪUUU+UV\ZWUU*ꫪUu]*jW\UUVrUU]*U.tuU]Z.ZrT+TUZUR몥JK]UUjʺZUW.U]UUTUuRRrTUUR륮UUUjUU]UU]J˪UU]UUZꪪUURuUUUTr֪UjJWUUJUjTZK]UUUURԫUUJRꪪVRuRUK]U]KU*RUZ]UWJUWU܊WUUUutUWjuU*ꪫUKZZV\UWKUuUURծjժJU+*ZuJJUKKZUUuJ.RRRTZUU.ꪭ]UURuTuUTVWUUUU.\UuUUUWRUUUR.UJUUU*UjUR)ruU.UZꪩ\*UUjW*֩RʪRꫫUR\jU*Vt*WUUrU]URWJVWUTJVUTuTWUUWK\UWU.Uu-rWUUUuURUUJ宥ꪵUUU*RUU].URUTUU*WUU*]*juuUu"WtVUUuUK]JRUUUU+UUWURWWRUJ-U.RRR]*-W*UVꪪUZuUWUTt*U.RUujrUUU*R֩JUjBUUk]UuU]*UU*WUUUURZW*U*U*KUԪUUKԪWKUrjURTu*TTU]Ruu+]*WUʭTURUUT\ծT]jWUu+uRjH]].UUU*\UkUuUUUuҮUܪ]ZUkUU.ꪭUUJꥪ.UURJEZZJWUUUUrUUUURuRUUUU]UUUuZJZUUU]*]U-U*ծ]UWUTRUUJ]ԺU]UUjJ֪vR]RW*몪UUUꪪUK\UWWUUURZꪪZuUTVUU*\kUuUUU.uU.ZꪪuU]JUZ\ꪪW*]U.ZuUUUW*UU*+U+J*UUuZUʪUZJVVTKrRVjU.UWJUUUZu.WZTUUUUU].UUtUUUuW*VkuUUUUTZ]UZjUWJVUU*TUUUUUTUUW]UUꖺ+]U.VRUuKꮵ˪UUuWKUWTU*uUWUUuUVVUWTRUUuUJWUTUkU*RVҥUUUT*UKUHUTUUUUuU*UUUUUU*]URU]u]U*]WUUUVVUUJTVUJUuUR*UtZUUVUUժUUUUUjj]RU-UuUWURTJUU+UTUWHU*U\JUTUWuU*uUUڭr]V֪r]]UUu*ꪫUrԊJUUTKjuUjֵuU*RZUUʩVTUURuUjVUKU*]UU-]U]JUUUUUUR]UUUUԹjjUURURKUU*UTVZ*JZU֤kT.UJrJ]TWUJUUUUUVW]UWUKTVkUUu*ZR]UZTujWUVKk]U+ZUUJU]VUUU*]UUTUVU+tUuUTUurUUUuuJ˪WUUUjUWTU*u]URuʫZU)U*VJUTuUUWRjUڪԪWURTUUU.]TTZUUTU.U]UJU]UUUKj]TVUU*WjUJU]j֪Uj+UTUUZUUUUU.륪UҪTHUJUUUUuKUWUJURUTuJWURVu*ZWUUUTUR*UU*RUrjT\rRUUuҫU˪ZZUUUUUUҪUUJUUUuHZRRRUU+uUR]uURTZUUUKʪW*ꫥuUUUVKVUUԺjRU*U]R]U\ZUURuuUW.UTU.*t\U]UUUֺUURUU.RUZj]UKUU*RUURꫪUVU]UHUTU]UuURU*UJUWJꪪꪩU]URURuUU].ZUZU.WUUuWu"Uu.Kuuuuj].Z]UuUV˪]JVUJUJURUUk]U*T]U]*jUUTk]UUKRrUUիUjZTRWUKUKJ]u*E.UUWRWUKJRꮵV\UkVU+jTKUTUZ]T]VtUKUUJUUZԺԪuU)UR]JUJ]Tr-uUUWVUUUTW*U*UURUZU*t]UU.]UUVUW*UTZZ]RZRTԮҵUU+UUU*UU"UUTUҮUWTU]*u*VW*UV]-k*UWRԵR*U]UUUuJWR*ꮫWWUUUTjUUUҺUuRZUj*UԪUJ]tR]UWUk]W*VJUUUZURꪪUTU*UUJꖪUʗUʪUUR]R*\U]VURRUZ*UUҫ-WUUUUZVU*UUUUUuKUURUTTUҮUKTW-UUkժUU*tUZ]U.RZUHUZrUUUTԫRU*R]ꪪꕩtUW**UTuKuWUUU*Rjjꥪꪪ*UVVRTjuUUWKZ\VUUTUU]]VUUuUKT]UTuZUVrVtUUUWUUJUUJ]UTVRUuU]U+tU]UU*U\VU]jRUUURZZꮪUjtK]Vr]UZUTEUUTrUVUtuԫU*KuUuuUUUu.T֪ruKVU"uUTꪕUUUUrjuuU*Ut]]]JtjWT]UҪUJ*T"TUjU.UUu.UURWU+ꪩUUWU.rժZUwRUT]*UWWU5Juj*U*J\*]UWUU\UUUU*UW]R-]W*UTujuUUҕtu"ꪪ*WT]KRUKWTUʪZUU]UTWU]URUZ].]u]UU*r+UUURꪪURTUuJ]]WJ*UUUU.UUuʪTꪥҥUUUtU]rRꕪU]UUUUUuR]R\TU+UJUUUUTU]UUUKUWUuUJꪺUUʪ]UKUUR%Tjꪕ]TZu]UjU)UU]RꪫWJUUJrUK]*T]UKkW"r\UJZU.uj]UURrWRZ꺪WKU+UTU**ԺUֺUUR.KUURꪪꪺWUTJ]UW*UUURRJ-UUUTUUKUURWUURjUU*UjUWUjTj]JERꪥuUW.몪]WU.JUuWZխjUUUTW*UJJUU*U)rUUR*ZUTT*UU]UtUUtjUUWTUUVUW+RRT\UR)uUuTZU*UZWU*ʮU+UVZJZꪮjVUUtUUUUjUURJU"-UUURUUUW-UUURUZꪺUUҪZ*RUUR*rUZKUUU*RZ뫺U]]UTUUԮҫ*]TUU.ԵҪRRktU*U*WR]jZJꪫ֮]\\uUUUZU]˪*j*UUU*UjRꪕJrjUJjJR]]UUUUK\]TUU\ZTRUuRWR\VUUjUrRJ]UuZVZuKjUUUWUWRuUWUTUuUUTUJUkZTZJUUuUUUuUU+Ԋ\uUVUUUU.UuUZUU\UT֩Uֵ]UUU.U\UTZZuT\UUJUU*]UTUTTTZꪪRUJURꪪK*UUVUU\Uҥ*juU*ZUTjUUuUUUUUUU]UUUUU]UUUVtt˩UuuUU]jKUTRU]]uU*ꪥԪUKURꪮRUUUuVUJW.ꪪ֪kZTUUUUJrUʪԭUҪ]]RUjZZUuUVU]uUJUUUJꪪUU]UHU)URWRWUUԺuUVJTUJԪUUuUWRW*Ԫ]\jZԺWUUjujUUrꪪuUUTR]jʪU*UKWJUJ\UUJTU*ZUJ]UUծZUUU.kVTUU.W-UUUJUURjUU\]+EWWJR\UֺjUr宥U*uKUWjUʪWVuUVUUUUW*ծu]*UUUVTU]*UKr\]TZu.UUUU]UUUUTuUUUUUjRZꪪWJ껩uUVkZW\UUwZUWJUJ]UUU˫ꪪuU֫uUUURU*U]URUUUkJE.UJUjJꪺ]UUVK-UJUukUTUJUUu]UJ]U.Urjk]WUUUjꪥUu*UUUUUUuUҺUUUJֺUvUVkUKUTꮺ UJTTUZ]UrUtU+UU.ԩUUUԵRjtW\ҮU.uWVU*+]W]KVtUԺ*몺U*]+]RUKj\WWWTUU*UUJUTW.U.*UUUUUWUիUUJꪩrRUU*TUUrꪪJUWRUJ\UUUU**U*UU]UꪩWUUUUW]*UrUURUTZUWKZRWURUKUURuUҪUJTUJUUWUkUUWU.ZKU*T]]JKVꪫ]UU*WUTUUTUJWKUʪꪫRUUkU*ꪥ*]URU*UTUR]UWWU+TJ]RժWUTtuԫUUUUUrUTUUu.jURUUUUUUujUUTUWJWRJUu*\UUuURJUUʺ]TꪪZuUR-UjTRUTjRuVUrZUTjZUUUTUWUʩUU-j]UUUUUUZ*J+UTj]UUUJ]k]UUUtU].UZ*]UJuUWJUZWRuJUKU\R.UjjꪪktUtUUUVWUU+UUVUUURUU.U]ʪ]tUUrGWJkWUԵUTˮUUrVVWKtUUk]WRUVUU*uZUUHjZr\ԪꪪWU*UUUUJZwUUUJRUUWJR)URUJUUTuUUUJUjҪUUKUZꪪUUԫv]UUUUKUV]JZuUֺUu.UUWUWUUʪU]UJkZ]ҪZʪUUTU˪\vU]Ju-uTUTꪪU.ԹrꮪuwWJjꪕUu.uUV\UUUUuUUUVU]U֮*URUUU-ZwUUKuVUTj]TJTUUUu+jWUUUtURK\UuUJ]KUUZuT]ZRjUUjUK\ҭtVuTʪUWJJ֪UJZRW+]-W+uUUԵjRV]UUURTrU]UW*ꪪUJVREUUUUTUZURruRu*VTR֫ҪꪪU.UWRUJҵUJUju*UrTUJ]UR\UJUuJꪫUJ]RUʥUJkUZUZUHUUUjUTUKծ]JJUwWRU*Urʩj]RUU].].U*]j֪UU"jUW.UjVRUUKTURUUTԻURUJrUUUUUURJRJKTURUU]UU]UUKUԪJJuuw*UjUWVVR]URGUJ֪WU*UUUUUUURUUuRZ]UU.]\kUժUT]U.\UUURtRUUUUUWUKVU.UK]UuUUUUuUZtUU]URUWUZZE.U\U.UvUU-RURRu].VuU]U\vUjUԵU.jUU*jJUUR5TUkʪrUWJ*]UuJꪪ]uU.\uUK]JWURjJ꺪T*W+ZZꪪUUUJUUrUUUJꪗRUUU]UTUU.uUjRuZ"ukRTW.KURrUJuUjUKW]UꮫW\uUWURJUUUUZrUUU.URԪWJkT*WRW+UUUUuZUTTUTUU.UUur]]UUUW*]*BU\J*UUTRU*U]UUU*R몪]UZꪪRU+UUURUuUUUUU"\kU\UU.rZUUW-UUu+jUUuRUUJRUZ]*WRUJꫪJ]T]UR꺗*UJ*]UUծꪵU\RUUUJUUTԪ+UURJEU*JUWWUURUU*UZZUUWZ\u.J-WWJW*tUUuU*HUTTU+U.Z]UUUZ\TVWVUUjUUUU]ujZ\URjTUJu*ZUVWTUTUR-u+RꪮUTJrUUU!UR]RUVuUUk]U+ZUU.]UURʥԪUUUUʪtt]U"]*uUWUj*UUZ\ҭuUUUUVUUJUuʵ)WWUu\UWUUJ됪UԪUKWKTUJW-]\W-uUWUUҪUUJUWUUURtUUZ]WHUVUUUUKUJ\r֭JZUVUUZꪕTUTU]*֫U*UZʪUҫ]UHuW*VUUJuUUJjUWUU˪)uUUZUuZZUWRRꪕjUZꪔ]].UjꪪTUխWUj\UUUUUUUU]U\WRuUZR]]KZԹuUUJuTUKUT.U**]-uUR]UkVUJ.uUJVUUUru*WKUjUUUU]UUUjEUʪUu]RUUJUJꪪURUTURUKukVUJ֩kUUUKVrKRꪮWTUUjV]URrtUWRRZU*UU.TZ֩UUURUWUTꪕURꪫjkUTUUTWUTUTZTꫪ-UUUU]*UUjU*]+U.*UUUuj]ZUU*U]R媪UT\UV]jҥUUuUJVuJUJTHKUԪꮪUVU]Tu]TV]UUUU˪UUtu\rU˗JUJꮥUUUUUUrUUUURUUUTUWUrUTUUUR֪W]Juu.\TR\*UURꪪꪺZꪹUtRuU*JkUU]UKUUU.]WJKUJERuJ*UrUUUUujjUu*]UWWK]KꪩRҺrUR-uUUUtUTUUURUj]Uj\UUU.TUjrUWU]TTUTTrꪪ꫗Z-kTrꫩUUȪUjTUW]UU*UUUrU]TTuRUURԹuUUKZURu)RU*Z֪URUZT]U*ꮪVWUUJUUUUUJJժUuR*W*UVUUU*u]UW*UjUURꪫUUUUkUkU.UꮩUUJVRUU]ʫj\U*UUUUvWRRU.UuUuUUUJZU*UkUU!TUJUUUUTꪗURU]+UUUU]TVԪTRW].]TUUR*!U\]]JRKTjUTURRUU]UuUUU*TUWTUUUuW]u*UWjڪUJUUUUUUUJZuRUUURu]U*UuKUJ]WW\UUTURUU.UJ*W*-\ꪩjUTUWKTUUTU*]U*ZUUJUUjꪩW\UTUUUUjUTUUʪuUUUkUjU*KZUu)UJUU*EU*\UrZꪺUK\*uWZZ]TUW]UU]+UTҪUUrk]UZU*UUrUUUjjRUUUUUU.WU.UuUJWT+].]TR]tUJUWJuJJTJUZTUuU\Uu]U]UKWTUUUUU*TUUUUUrꪪ*UUuRU\UjTҖjV\UUU]ruUU\]\U˕]*WUԪU*V]UJ*UUUZURu.ZJKR.RU-RTUKWUj]uUTUUuU]*UUU+UTjUTWJJW]Tuꪪ]ԪUU*VUUZUTԪRR]U]Rr]UU+ˤUUUUJU*UUԭUUTҪUUUU-j]UZUJꪪꪥWJuRUZUUUEUjuRWRꪕU.Uj*UҵUU*UVTUTJU-]UUZUUVU֪꺪URuUUUʥ.UUUUuUVKUꮖuRUZUT*]URZU+UUZUUUUUUURU*UHuUURUZUUUUUWK몥UjR]JjWR].UUUUUjUUUUKUUtUUUʪ\UU\UT*r]UWU.]+RU]-k\UUUUUJuUKtUڮjU*UUJw+]VUJuRꥪU]]VRWUu.W]jUT uU]Rj]UҪZ.ZUjVTrUuժWUKUUUuRu*ZUUu.U]TZT]UUU]WUU]RUKVUWUuTUUTuKUjKU.UU]U*UJVZJUUr\KW*]RUUUUjJWKuUUR*UWW.UUUURJZU֪UR\UTWU*]jKUUW*UUUU"UZꫪUUUUTڪUUJꪕTԪUUUkUuUjJU+Z]UURZUJU.UJuUZUR宕UU-UUUJtZꪪJUUjVtRu-UrUru]]W\TJW*+]*R]UZWRUZJEUUT]*UjjTZu-K]uuRUUURWUur\UʮutU.TV]R:]KRuWU*JU+W.WU*WUuURUUUVjծҪUTtUVUUUUUR%ʪ]UVJꪺRRuJU\֪VWUWUUU]UKʪ]UjUKUZUHWVUK*uJUҩUTuUUUU˪*UUUuRtU˕r]UʫTR.WRU]TʮU]UUUUt]JUZTu]JU-TRԪUUURWUUUWUUU.UWUJEjkUU*Z*VUUU*U]Uruj]*UJkZWUUUUUUuUUJ\UʵZUU.ZWRJUUUTU+ꪪVZUVUUuUjꪮUuUuJJꪺ+RUUUUU.UURURUU]jZ].UUTUTURV\uUjTr)]TU*ꪪUuRKZ]k]UUUJֵUJZU]*UWU]UKUU.UU+UUUUjU*R*jU\UUU\tUJUUKVUUJ]uUUJEKUU*U\]JZUUUU-uRkUKUUtZUKUWUUkU]KUrJE.UҪֵUUU]JUUZ]UUVWUT]UUrUVUʫU]KZUuTuUUjVUUW\k.tVZ]jUUUURU-UUkUjujUUUUWUUTju\*]U)UJ**]JURu.jURZjUu*\TTUURURU-UUUUUJ]UVUJUURUU*U.ZUUUUZ.UZWRU]J]UJTURZ]*UVTrꪪWKU]URUUUU*ꪩjurUTUJ.VWRUUҪUZUuJʩkJ]T\UZԪUUZUUUUUUUUTUUrRꫵRW*UJT]jUU֫W.j]JUUKWUURTUUUUTUUkU]ZʭwU]jTTUUUUtUWUUUU*U"]uU˪jʪVW*ժꪵUҪUuURjUUUJUW.Wu*ZuJU-UTU*U.VuUUW*UZJ\UuҫUZ֩jktUKVjU֪]*UڭjtZruUU]-tUUT\ZTUU+U]UUʥWUujUTUUujrT]UUHWTUUkU*tUjUJUUTT]*UT.UZUkUTUJ뫫ZkUUJW*ꪺUUU.U]*WUUZVr]U*RrUUuU]*E˪UUWUUWWUUԪꪮTUU-VuUʺRWRTUWUTUUuj\RU+\U]UԮURuVuU*UUrTUT]U]JUUZk]]+ZꪪEZ]JUkU]URUWu*UҗRuW.]*tUʪUJUR.ZUJU#UUUuUUUuKZUUUUUuUU*UUUU]UZUUUR֮uJWUUU*ծUUUUUr:UUU+UuʩUUWUrꪗ*jUUUuUUVU\rUKjUV]JUUKUUUUH\TRURTUruRꪪԪkZUJ]UJZuUJUUҮ]UTkEUuUUU.UUUjU\UU*TRUTKUJUTU]-UUUUUU]HҵUTU.WUuZUU]U]TꪵUj.kUKZUUUkUJUVꪪWTRU"Z֪TrW*UUK\UZrU*K\jԫ]UuWVWRU+TU\UUR.]ZURZ.uUtVUTUU]VRURURU+ +U.UuUuUVUUUUU֫]UKT]jԵrwUUVUUKWUUTjKWKRRjUUjUUUuJꪪ]ZUZUJuTUU]WVjZUֵZZEUWJUUUUUU.rUUUZUUUUuTUJ]UUJrUurUUUZ]*uUJUKUURjګ˪].ZjUU.UjVUʫUUԩ]JURW+VUUHUU+UtJuUU.UUjUUJZZꪪ\u*UUUUWUU]uUUUWUUUEUWURԪR֪JTwW]URꪪrUU*UURҮ]UWUuUURURWUTruU) ]U*]URJuTUUUUJUUJTUUURҫUUUZUUjWU.U*UUUUUHUU.UUUu*ZUVuUUU*VUUʕ֪Ur\UZT"UU]ZUUU*JtuJj\ZꫪUWUURrUJ-uU+UT\T]REUR*uԪ+ZT]URUUjVUWU*TU*j]UUWUWUZk]UT]UˮT RV*U*ꪪuUJUUWU.]T]JUTTTUUҪ]uUuUԪR)UurUuUUuUR*UUUU]UWUUUUV]KUrKrUUjꪭWUURj+UTUURUUTu-jU]TZkUJ]T]JUUJRkWUUk]TjUUUʪUZ꺪ҪUUW*Z]Ԯ]\ֵTuTrԪ*RWJ]UHTUKUjt]jtZ֪]Kꪪuu+WU*uJuUUVT*W*WUHVUTrUW.uuUUUUUU*uUZr]TuJꪥ\T]UUJ*URҪU*RkUUU]uRruUZWRUUUW-U]*ZWZTURwRUUHkTꪫ]U]URUKZV]]uRUUUUuUUURUUTWU.]*JUTUUkT.jZUT]RUKUUTZ֩ZȪUUUvuJꪗUZWUWUV]U*u]Ur].Uu\uJU]Ju]R]WUUUK*u.ꮕ]URR*UUURꪪUJTUUTjUTjRUjURV\u*UUU+嫭kUJUURUUUʪU]TVUuTTJR*UJZʪR]UuUҵuUUJU]UU\u-uT]URҪ]*ZTUUJUuTRֺuUUJUU]*UUUUUUUJ]UUժUTVjT"UUT.ԪUTUUUʵJUU+JRTUUjV]UJU]Urꪪ*uRW*UU*U.juUWKԩTrTU.RꪭjګUkRꪪJUUUUTRꖪUJ]UUUUZ]RꪪUWKUURk\UU]KUUJUVRUZkW*UUUUU.WUUJW]]UUT]JVu-\VuUJ]UUVU-jUUUU*UUU"UUW.UJTUUKꪪUUUURU*]UUUUURUUU+UUUKRUt*ꪫtUꪹk]WJUUU.UJꪪUUU.KTUU]*TUUUU˖U.]U*UUU*몭W*UUUUVUT몥uU.Zu*UuUZ]]ZuHUUV]UT\TUUUtRu+U\URֺU+UU\UU-ZWUUʪV\]j"UW+]RUW*t˪WUVUUUUUuVUJZUU]jWJUTTUUuRW"]U.*\jUVUUʪrUKZVTuJUUTU+U]JUUUZZ]RUrUUUtWRꪵJUUTrU-UUR꺕RUTTTUUU]UU.UUVWTjZUUtURRKuUJV]U.VURUU]JUuURURZUZtU-U.]WUUUURUR*ujꪪ]R\\UUjֺUrU˫UV֮JkW-T\UԪUkURU.]uU]*u*RUjꪩRZuUUjUJU)WUUjUԮUUUHZuUU-uJuRꪪU*U]ֵuҭTUUUuKUTU.U˥RHRU]Uju]*rꪪԩuVZU+UTW+Tru*URꪮUWUUrUUUҫTZBꪺժR.UUU)jRuUUU)jjUUU]UUUrUUUuJUU!URRUUUT\UJUUJUWUuRUVjUTTrʪUUWTUUURԺWTUUꪖUUWUUU*ZTRRKT]UW*UʕUʪjUZ]UU)j]UUW*WU]UuUUZUUUZjUUUVURuU-uWURUUUUjuJGWJw*ꪖUUUUU+UJRuR-ZUWT]uUjUjkWU*ujU]WR"WVUUU*kU]TUTW*URUjWjZUUUWJUUJ֭kjR\T]RWRWUUUUU]UUUWUʫW+RUJUTUTTUrUUU*U*WTUUT*+U.uU]]U]UR]UU.tkUUW.]WUW.ꫪVRԪt]+]Z뫤ZUUUJuR.U*URꪥ˪WUUTUUUJuU*KTTW+]KUUUW]KUH]j]-uVUURuRUTuU*UʪUZRU]JRUUTU*KRZTUTJH]UuU])tUT.KJU*֪*kUJUUUUU*ֺU]U*ꪪR5UUW*T]jUU]j+UuU]RZVUUU+]UUUUVRuUWURWURrU+ZUJrJUV]UTTW+Z몭U]UKUիTu*TuUUrU"UTUTUTuRꪺU].uW.]u]UUUUUkʺUԪuUrtUHJ.UUUUZUUUTJ*\]VUʥjԺtꪕTu+UUU]Vu]RU*UJtRRUTUZ]UkURUuUUU]UKUuWUU+U*U]KUUUTU.VjU]UUKURUU]JҕUUuW\UUuUrUUJZURUUUKU]JJUUUZ\]UUuKTԺꕪUKURZUjTW*.UU*ꮪUUUURWUUW*URZUUU-UJVUKU.rV\VjURBU*RUJ*ʪWU]UUUW*U]UUUVt]WUUU.]*ZURUUURURJUURRUUTUJUUUUTRUU]Uʗ+UUujUUkU*U]UU-jTjTUUjUUUT.U.UUUVUJRZT]ruUtj]TUU*EUUUU*UU]TJUUҹJ*jWUUժjJU*wUUZ]UuRWU\utJrKꪮVJrVUUTꪥuUuU\uUU*UUUU]U*THUW]JZrʪRWJ]UUU+UUUUZ]UT]UU.]UJuTZ]JUUUTUR꺭UUJWKRUVZU*TZWTUURUUURUZkUUu*TUHU˕ʪUUKW]+UUUUK]URUU֪W]R\UuUUU*uRUUuU*]jҩW)uU.UU\RuTVԪֺW*UTUTU*UWJjUJVu]URERUUUrꪕruWUuԺTUWUUUUUUUUUjJUR+]kJW+UU*UUWRUTUVU꺥kUr*\]JꪫUUUK]U.U) +rUURTUTu*ԺUrUWUuWTu\]\UUU.RKTꪕ*]\uK\TUUTZrTuUWUUUU*UZ]jU*kZuUTURծk].]WUUKuRjrUUTkUU*UUԪUJWU*꺪U*UTWVjZUU]UJꪪrU˔WUUUTUUZUKTURrUU]RJ]UUUҫRVK*UUU]TUҩRKwUծTUUUTUuWURkU#UJֵUURUJuU]RZW.]U*U+UUZUk˪URURUUJU]-UUU-TUUTUUWJUUu*U]*RꪪUU]UKJjZ]UJ\]Uj*ʪuܺKU]J]UUZ]WTuJkZ\rKRUUuUTUjuUʪUuZԫH]RUJTUVUTʪVKTjʫUUTV]tUUu"UjRZ\UjUUUtZ-UUUUU]JjRuUUJҥKWU.+TuW-JU-kWZuT]W*Ժ]tu]UWUUUUU]*]U.u+UW+E*\UU*ҫURZt֩ujWUUʗUUuZU\UUUTDkKT\꺪]TԮRTUU.t]TUtUtuUTZJHWUUTrꮪVꥭjUkWUUUTUUUU*UU֪UUUWT֪T\kUUukUUTJꫪꮫZUUu+URKWKU]+UZUJ]U+VuU*\KURUUUUKJURU.kuUKZR]+URU*UVjU˪URU +ꮥUU˪URTUj-j.UUUUJ\UKUUW+ZJUVEU.]UUUUUU*UTTRjTWUU.UU.UWU\ZԩRj*ZꪪUKW*R*UUU].]RRKWJV*TTTUU]RUZTZVUUUUT]U*TE]U]K]UUUUUuUUjuUUԵUUUuTUJZujrKU*UUWUuTE*jT]+TURUW*\jUUU)uZtUjU\UUW)UUʪUU]UKUJU*UUԉU]U*KҫUUu*UTURUUWUUUUʫ]TUU.RUuWUU*u*ꪪETUUUkUꪪuWUUWJ]URʫZU\rꪪ]UUT]Ut"RuU+TUK\UUUUJU*TҺV]ZW.UZ]T*ZZjUUUuj]UU+UUUKjUUuUr]*TuUjꪪKUWTT]R*JUUUwUkUUTWUUuT]VʫjUUWUJj]UWUuURU*Ur9UԫUUjUUWJU-U.Uu]U]uUU֮UURUUTuʪUWWUKW]TUUJERuJkUU\UKWWUtUrU]UKUuZuURU*ZUUUUUUUUKRUuUJUU*UUUTU*UZuU*jR*Uj]R]+UUrUҫjuHRU]U*UZ몖UTRjUʪUUUT*UW+]]kRRJUUU*UUWTURZRW]RUUUjrTVtTUU.EUKuUU*]uRUWuVUUUrUU*jW*RUԪ]]UUKURUWJDUҵWUuꪕ]RZUJ]UjWU]]U+UTu)U*v˪RuURjURUԹHRUUU*U.UʫWRUԪ.].Ur몪UTuJU*uUukTtjUJjꪥuUZ]*UT]UU*uUU.ԪURU*KuR]-uUUUK*JU#UU]UrUU]U].WUR]JRUUWURjUuJ˪UUW*UUTZUUTU"tUWKURUUJ]uUURZjUZꪥUUUU]RU-kUUjꫪUUjRrVUUJZWUU.jUUU*UWUUUʪURtTURUUU]U-UUkZUuԊUJt]ʪUԭjU]RR-]*ZU*UUU*UUuU.TUrEUUrU*KUUJUJuJURꥪ֪RTZꪫjUtJUUuUtUUjUuJURꪥRU*WK]]U]UUʪTu*wRZUju*TK]Tu].WR+ZU**UUTVU-uWUUUUUʥtUUU*UuT-UREUKVjuJR]UURRuUUU]W*u*U.URRUUUkUUҪJU]]U.UԺRjZkJZZjUUKU.URWWTT]UUKUJUVUUUJWTUUUUVUWWURkuUkTҪW*UjURJUZTWUUU*ꪪJkUԉWUUuZUUʪWURU]UUUU*֮U*UUURUUVWUJꪩuR]UUʵR+VZJW*UUʪRUKjUU\UjTjRZUZ*UUU]UuUVVVTUUtuUUUKuUԥUUtUU˪UUk媪jURUJ]UjUUuVJUUZ\jUkUJUUURUW.UUWURJU֪ꪪ]JUꪪWRuU֪]UR"JRuuJu*ꮫ]UTU.ԪꪪUU*UUZuJUuuUUUUUUTJUU*J]WRWRUUKUUTVꪕUWUUuWUUUWUZU]+T*UTjUUUUUUVKZUUUrUTjW*ZTuuUUUUʥU]UU.ZUKTuRZԪҹrrRUZHU]WJUUWUZuUR-UJUVUUTUjֺTW*ҪVUUUUTR]UjuU*tUUJҹ]TUTkWRWU]RZUjU.KRuUT]+*RTUUjUJUZU]*Uk*]*U.UURjkrUʮ媮TE\\uUVUJRꪕUU.kjUjUVZTʪZZTUW*\WUU]իJUWUJJZ.jUUu*JWkUJKTU.u\WTU-RTUUU*ꖪUR\UUU.ZURkr]TKUUREtRjUUUUUUJWZUKUWuUUWUUR-ZUUUUZUURRRUUWUUUuUUUUZTꪗ*ꪪ]UWU]Ku]UUUURUVTUU. +]]Tu.UKWUVҪ]kuUUTUU*\UU륭jUjUW]vvr딊ꫩj]]UUrUWR*]ZUURjJꪪVJJUR*uJ몪ꪫUUUʪVUU]JU*ꪩTUuUj\U]U]UjZꮑ*]UUUUTUUUJUʫ]T\UrUUUUUWUUUUWUJ]*Ukt\Z-UU]**]jZTUKRUU*KRVWUUUҹ]UURUuUTR*TURTUUJUJJ.WJUU.URꪪuUJTU*U*]HR˩rk+uZZu+U*UU*uJt]\U*U*UU*r\H.UuTUUUUJZuW+UZZҺZWRURuJ+\"*UUUR]kKW.RR*U]J.UUZUUUJjWV.TUK\uRUUUKUUʪuU*UU.]U*UkTꪕK]TUTtVUUUjkUUHJUJꪪRҫW.UU]UJ˪UUtUUU*]UU.UUUuuRkUuUU*R*]U+uKU*UUJtjUuU.VUR]JT\Zꪮ\ҪU]J]UUUTVjUUT]JUuKTUUZ\UUUJ֩uUUUUJ"UuRuUUTUZUUUUr]*uuUJUVuWZ֪kjR-kUU*UU*UU]WjUWUWTWRꫮUuUZUW.RUU+KUU\t +UkuUtʩrUTU\]uuժUJUU]U.*uZ]JUJ\ꪪUJV˹UUUUVZ몪URWUUW*U*U\UUUUUTRUH]RUUT]*굮ZW.ZUUuZW*U*WUZ]Z֮ZUU*UBRjժUU*ZUuR]]UҪʪUUU*UjUZjUTꪪuUTT]UUkT]UJRUTK*몵U*UUrUTRTU*UuTUW*UVUUu].]JUTVu]UuUUuJ]URUuʥUREʪWUUu*JRRUUUkRuUjUUUkUZԪVUJW-VTUWRZUUUZUWUJW*UjURUU*U]U]uUꪪTֺW-UTUUVuJrUWRU*UUUWKUU*U]ʩR몪UuUurURZUtUjUUUTu]UUTEUU֪ꪪjUUJTUUUTW*U˕]RҪԵZUJ*UU*\ԵUuUUW"ZRTTjZ.*]]UꪪUU]UUuUUU*ꪪUWUUKrUU**BRUU]RꪮJUWUTUZ*]VRu.UUK]RJ-ZR.]K]]]u*+VUuUJ]TjUuUU]-jjut]WZUծW"UTuUTUUUuJ\uUUJUUuRꪔRuUU*UUUTWJUU*UWUTu*꺪WUjUUJuZUWUKUUUԪUJjUTRUZJRZ+jr]VUUKUuuUUTWUU]-UTjTWURUUJ]WUUWUWU\RZ])RJUTTZ+UuK].uRV]UUUUUJꕭUUUVZZUJ]WUTBU\ꮥVUꪺZtUUU.VʵUJ֪Uj*UHUUUUUJUUURUUZUUUU]UwUʪU*ZUTU*tj]tTUJU]RVUUTU]VT]jRUUKVU\UURuuURJ.uʪtr]\U\Z]UZUUU\jUUUURWUUUURTRUR꫕WHURUJRU*ժU*kj\UUU]UUkZRUUTUU*URԪUUҪֺKUTUVVU*]U*u.U+]UW*UUTUZRꮪUj]U-uJU+-rUTR]UWKUUUJURRꪺU.uJW]KUUjUTUUjUW+JuUUUTꪺWUUUWU*URUUUUjꪥ]jjZWUtV*W*TVꪺUTRUUUUUJԫUUUUTWUuuRWR]tUuUUUJ\WU*]URkTR*U.ZuJuU*UUUU]UUUJUU]UUJUUWUuURj]UWJBU*UUkjJRUR֮-KUTTUjTRW]UUJR+UUU]J\W*UTꪪRr땪UUUTUUTJJꪪrUUUjUUUUUUJꪪWJUUUR]-UW.*]UJ*UUJur9u]WW*Zr*URURUj]uRUUrKWUUҪU]UUU-RꪮUU"UժUʪUUUUrVURTUU*U]rUUUkUUUK˕U*]UUURUUtZUTWUWUUUJUZUUWJrT]KU]UJTUKkZ֮UU*uUUUTUUU.URjrUUWUUuURjUԵUUWRUUJrWU)jUKUVuUUUUUKUUUUUJZt뮪ꪕUUJTUUZ.UrUU\]JUTURꪩVRUtU*UUҹUZ]URRUU*RUUUu*UUҥUWUUKUURuUUUUUUUUJjUUuuuUUZURꕪWTTWJԹkuJUUu]R*UVRҪUtZꪮUU*RꪪֺZ]UUKTKUJ]U*]R%WUU]tkkUʪժW.TUUUUUTUUU*宔UKUUJTUWUUUUR媩tUJuTUU+RZU]URTꪪRkTUծZZUUj\WTuU]RuT\T*)UJKZuUUu*ZUUʪUTUKU*uHU*WURUURVꪪRVUWKWRKUuUUU*uUrJUR+RTTTʫ]RuVuʺҮ]URUW]RթRꪪUUWUkU-jZRUUUUUWUUU*uUURꖵ֫UU.]VUU*UVKUUZ]ʩUR꺪j֪HrꪪVRUUKu.R]-uZ˪*UjUUUUUVUVUU\UUu.ֺUUUKUUU*uR꺪RUUwUUUU˪Zu*ꮩURꮪUJ֪UEUWWZJ]uU*U*UW*]uUjUUUUR媪ukURV˪URꪕR몪uUwJJ֮U]Z꺵TtUtZ-U+UZUW*R]UU*W*UJꪪu\]WVT\]tk]+VZ.ꪪWU*kTUUUUUT]juR]T].UUTUu]*ZJU]UUʺRUUJKUUU"UUR.]Uuj]TKuJUʕZ몪*RU.ʪWUUuUUJU+UUUVGUUU-tZURWUUUU]]*UURUTVUZUUUTUJW*tUVUUUV*uT]U]KZZUU*uWUj]UKVWUUR**֪]EUuUUUU]tʪ]RUWUU-TKTUTk]VuURVJT]K]KUTRUU*ZUK]U*UUUWUUUU*tURZRuUUjuUuKuuUJuUUJUR:]UUJWUJWUurʫURU]\UT]Ur]UUU*ʺWVU-uH\UJW*UU]UU*UUU*rUKUU**URUjUUJU*u*UVR*TUUԪUUUUUtkUҮVUUUVJUU]UZWUrJ*jUW*.kUU]TuWU-]U+UUk\UʪUuUUUu]UJKTUU֩u]j]U*URUU.UHUUWuUUU*TUT-UUUT\]TkUU*UURUJjr֮ԩ]*R5*ZjU]UKVU+U.UkTRUUJ]UU-UUTUVVJ]UZꖪ˖*UTUUUUJw*W+UUTZ*rUU*ZTURZUZUZUKTu*VUUUWUUUUuUU.]TKTWUKUZVU]UUUR˫UUJ+UTR:֪ժvҺUUUUUժtTUUZZUUUUUKZUUUrtԺH\UuUZ]JruԺrꮪ*UW.]RuZVrUTTW.Kk\UU"Zu˪]UҪurUtVԪ)]Uu]UUUuUTWUUURuR꺥j.UKU*EWUUUU]UuVURUjK]\ʕUkuuZJUZREURTu]]R+UUU-UVUtTꪪ媪ꫪU\\U-\UUU*UURKUUU-UꮹjJWJ].UU˥ֺ]UUK\UU˕R]TUUURUVU\UȮUU]WZUKKkTZUuUJjUUUTU]-UUUZ֭u*\Ȫ]U-UUUrRZ.UUUUuUKUWUWRUԥUUR꺵uUUUU.URVVButUVutUt]UU+UUUuWJR꫕ZUUU]u֮Uu]URZUUTRURUVUUU.u+]*U+tuUuWTUkTZRUU]UUUUUU*UJ]uVH\tJUWJuR+WT\]UUUUJuuTꪥUuURꪕjԹ]UKWTZꪪWUU+J*jU*ꪪUKUUjRUUrkVVZuUWUUT]\U]TujRjRZUJU\Jr]u.UWU*WUUZ]UW]URJkRUHZrUUjZUZTjTU-jUuUUU]U\RU.]UUUUWUWJ.uR.ꪫUUuwRU*UUjUTZuVtҵUWJUrURURUUJEWJWWUUJjJ몗UZUU+uURRꪩjUT]KU*jJUUUURTU"TuU]+W*VJU*VUUUu]U*VUZUUUUWWKRUUK\URWWrUKU*UWUU-]UJꫫWTWTUUUkUUU+ZRUJUZԅRVU+UWUUUrꪫjꪪRUUҩVuUUR\TZꫪUR]UJ媺ծEuUuu*UjU*J\ꖺUWTuUUjUUUUWuJ\.uJUTUKTꪪUuU\]UUURZꪫuKJ]\]UrZ*UR\jjTUUURWJJT\ʩuJrUrrZVUUU*]RUUUrU!U+UUuZZ꺥KuU.ZUTWrj֪Z]W-uRW*媩U*TʪUKJZUUURUKRUvWVUUU*TUu*ZUjuUUU].uR]uJ]JUUUUuR֪UUUU]ZUU֪TtUJrU.]WUt].U*ꪪUR]U**RꪪU]UR\RZU.UUUu*TuUTZꪪZZU!u*U.UUKUUJZꮕRUW.UU*uKU*UUKT\֫]TR"jUꖺWUUUUJUkjUj\u*TU]TZUtjWU]UUUW"TU*]*Zꪺ+]KZU+UjRWUUUUU*\UUR%WUTJUUU])uU*rUURW*UR.UUJVꪪkUUrUUH*+TUUjꪪUTUUTUUUR몗JrU.ZUUUTUUUkUTWJuWHUTJuR].Jꥮ\uUJrUU*JRUTt֫TUR*UUZUJUtVUUUTֵKVUU\˺UUJꪪtEUUUUWKUZTUj\ժꪪUuuUUUJWRUUVWUuUUUU]UjT +UZr]V]]JkUU]UZZꥪjU*WRUԭVZ]jHurUUUJjuUU.]ҪVUU.\UTUU*֭U*ֵujrURuUUUUU]RREU-UJZRUUuUUں]]TZUUUU*WWrUuUUUUUUUuUU-jU"J]uR\\ʪ\u*U.UZꥪꪪ\uUUuWWTUZUURUkZETTUꪕ\ZU]RUUUJU*UUUTJUKWUUҵURUUZZKW+RU*UR\ꪩUUUU]KUUJr]*jWJUҪUJR5UTUrUrWUURVUUUUJZVUuWUURZ]JUU]UjRUU]t\TJJUUU.]JUTԮUWV\UV\UURUUZW*UWUUWUU**KURruʪ]Rr]TU]ҫUU)]U˺UUTZU.UUUjrZURꮪR+]RUtTrUZUR]U*ԵuUJUU+TUUURkHUWU-jUZUUUUU*ԫ*UUUURu-RꪪW*URRW)WTUT"U]TUuWK.VU]*UUUU+UUU*WU֫Trt宪ҺUJEUZUuTkU*tVUTkUUU*\U.]-\WUJꪫ]JUuUTUUUURWU*ZURUK]UK\ZU\]媩jURꪪKZUUWU+U!U*UTʪUKUjUZ]kkUUUUUUUUUKtuU]UVUtUU"uuUT\trW\Urժu*]kUUUUUUU]RuU]UU*JUU"VjTUUURJVT]Uj]*UTUUUUKuUrꮪԪTTRR֪uUU˪]RꪪꫪWUUTZժҫ]T\JTUT֪URT.]R]TU*+UUUrutꪕ+TJUUUjҪBUꪪWUUjWUUUuWUURuUurURJuUWJKk]]TUW+ZUU-\]UWTUTUuUֺUZU]TK]TTjRԪjZUJURծBꕭTUUZꪗUUU]UUWURWUU].uWRUUUTU*֭uKZꪪꪕꪪUHU]*TuUUJ.UU-UU.UjrZUT]URV]UVjURUR+jr*U]U.+*UJUTVUU]U\UVuUUUU.ZU.UJrUU]U*]T]ֵuVJ]WVWUUuUJWUUUuujTZUUZ]JuUkVuuUԮ]UUKW]UZtuUVZֺTuWtUUUJU]+]]U+UU]HRUUWUUUUUU]uU.KUu]WURVUW+RUk.WJKUTuUU+UuUU!W]UU*UUWUW*U*]WTU.UZUkUKZRꪹuUUUUUu*VUUUUU]R*j]UJW+JWRURUJUKUuUUVUUUUURTU+UU"]ԪUZUjUURUZK]U.UUU*jUJUUU]V]W*UjUUWZUUUUUTUKUVu*JRuUUUUUJ֪ԮTժUUUURVUҪRJVTkZu*KU]WRUWU*ZRUUTUUWTUҪUR*Z\WU.UڪUkU]UTU+ڪWRujU*VUWUuuU\URꪭ]jUUUUUU]W.]T-TkUT]UTҭUUԺUUUU.WRUUZZWUU*UU-W.UU.R*Uuu*uUWJUURꪪVu]\rZuruJjUURꪮU+ZR*R]WU].UTZUKUZKԺuUUkVUW*RꪺU+RWHkVuWR\rrUU*]JUJUJTV].TjZKUR)uRW\.RꪪꪮUUUTRtTU.UUUUrUU)kUUZ*]Ԫ\WUjU*]KUUUU*ꪪTUURUUU*ԹUUUWTUjtWUKZTVrjUUU]Ur]WJtUU+UUUU-RꪪT]RJkUUUUUZUTUT#ZTVUURJZ\Uj]UUJUUUJ]URZWTu\꺹UU]*HUUUUUVVU+]UUUT\UWUUJTZ\URUUURUJ꺪r.UuVHJ嫥UUUJZ몪U*UtꪵuUUZjUTUTUUURU*uUUkZUj˩jRZZJur]*UUUURuUURꪪUZHZZZUJ]UU+URRUrUUUUJUW.uURURUT]RUuWT#UUWUU]J\U*ꪮj.ԺUZjRR\UWUʕʺuTJꪪZTJTTWJuUR]KWUUTUUUJUUjR֪U\UժZֵ"jU*Tu+UjUR]UU+UukUUUUWKk*ku]k]JUU\\"uUWUWWU*U]URju]UUUKҺU.uTRUVRUUU-u+U.ZuZUU-rU]WUU.U.uԹV˥WUuU]UU-UUUrUUkRRꪪRUUKZUVUU\UUUJ\UVuUWKUUUUU]UuUUUUU\U*UU.uUUUUuJW*U"UkZUrTjRUJURUwUjUUUU*UuRUZU+UuuU*Rj.U*\uUJRUUUUZKUUUUUU]UJ]]UZUTuTUҪZWHuK]UURꪵU]UURZtrU+UKKuUZUTUUjVUUUrEUtԫUWTjtUW*TVUU.UUU*UJKZZRUWUUԪW\R*UUtUҕUUU*ꪪUUUTV.UJUjUUUUꪪ*UUUjZuJRU\TUUUUU]UUJRUjԮUUJRҪ]UjrꖪUUUJKU*֪RUJuU*UUuUUUJUJZ]TUUJuu+ZWJ\]*]UU!u-RꪗT몺WUJK\U]TUJjW]RUUJZ]ԵUR%UU]*˪WU)UJU*]UVuJ]*UU\TUҪZUUUUUU]U"]RURuJUjU\UU.UjRVUUR\ZWRUURUURjWUUUUTUJJUjUuUjUUkuuW*UjW]UU*TRuU*UtRU*Rk]uU*ꪪUTR˪\ZʪU]UJꪪZHU.UUJrکjUKu+TJUWr*W]RZURUUJ宭URԫuUjW.]rҩUTjҪRZUrUUUJUk]U\u*EJTRUUJURURKKUUK*UjRuUUR.]UK]UJUVtUVuUTRu*Ukj]jJV.UuRUUUUV*U\UJR:UU*꺪UJuUUUڥUjr*R]RU.UUUUuURUTꪪEUUJꪕW.UuUUUJuU-ZuUtuUUU]UKUjꪺU*kU-VUu-jUUUJK]"UWJ\Ԫ]JU.UUTU]-k-UK*URUUUUZꫪ+RuJUU*TUUUZԮRZZ꺕jU]UTZUUUJꪹu*WKꖪuWUUUUUUUUV\uU]UtUR˫*ZUUZUVֺ]UrUUU]JRRUUUUUuTUUꮪJj꺗UUU*TUrUkUUJU"UWUKU]UUUW+ZtUTrWUJjUUURURW-U*UJU*UUU*!kWUUUҪUUUZUU*]*W-UTR]JUUTUUR.UWZUKZ]KT]RUR]U*rRTUj]URꫪ]*Z목uUUWRUjZ]uUUJUҪʩ Ttj-U\jwTUT]UUVw*TW.\]JVuUʭVkrUUTUHZUUZUUUWUUJWUUWU*]]JUJUKTWUU]*jUU+k+!jU+\WUtjꪥUUUUURꪪUZjWUZU]UUJ֪]Uu*uUUUURUJUUUTUUJUU\JZ*TUJRԊUUUUVUWUUUUV]TuJuUUU]]+ZuJWVUUjjuKU*GrUTWuԪWj\ҺҪVUuWUwJUUUVuZVUW+ZJ]UTTkR+uJU\WTֵUWUW]VꪪWJJU+UUZ\UZ]*T]-jU*WUTWTUU˪UKTԥꪪ]UKJrrUUUUU+UڪT]RꪩZUrUJZWUvU*VUUUJ]UW*]R*jUuUTJUZUJGUUTW]ZuUUr*URUZUU]UʺruU.U+U*.W].UU]UTU*˪UR꺕ʺUU-UUURUtUWUU*]UUURuURRUU+UUUR*ZUJVZu.UuuUUjZ*]UURJU-UUuT*W*WUUUҪ\URJtHWUVT]UUUU*R]JԪUJUU*ujU\Vu*JʺWUkZҫR)UURUuʗUZTUU*UrUUWURꪩu.JUj-UUUU\ZjZr*VWJUUUrJWWUUUjWZU˪֪ꪪjUUUVZԩUZUUUR*]UUJVUUUUV\\U.URUTUUJUUUUUUUTVUUUW]UU*kZʪURR֪k]*J\ZU]RU"UUUUUUU*U]UUUUU\uUTWUZURWVj\UURꪪZTJW*UU.U]*UU+UZUJuURt\UVWU]RUjUUK]JWUU]*UrtUUUUuUUUUUT]Uj]UuUUVUJUU.*U.JEZ-tUUU*U\URVjUUUUU.ʪUUVU]WU-UUR9rURUҪUU]UWUUZ֪u-UTUUVUUUUUrVUTTUU]uU+\UK]UUUTUUZJZUUu-UkW]R-uTUUUURWUUUTUUUHuJ]]UJu*UKUUUU֮UʫZKUW+u]*WJURrZ"UUU֩jUJʕUURʺR몗J֪WUT]Rꪪ˪Z]UTVTUU]KUHujW.ԪuR]*UR]kKW\UUUWJꮪ]ZZWW*UrUU*UuJUuUUTUUJrUVծUUKR\VUJUWJ몪꺮UUHUZU]UUTuUUUJTUkRUUUTU]*UW*֪UWUuUtURWJRjZꪕW*UUUU+.JUUURukj-UKUUZ*UKTT]TuJUUjuW*UUURꪪURURUVUUVu*uuWU.ZUUUUUVTVUUUuJU-UJRWTuUUUKUt]UjʮҪ*j]TURUU*WWUURUUU]UU.u.UUʮTꫩuUUW*]UkjURJWJꪤTuUjUUUJVUUUUj+UU.ZU\uwJ굪UTZ֪u"]U*UUҥURꮺUUUUW.]UUUUUUUUZUU*TTu.U\R"jKuUUTT]J]uUTʥUu.VjUJU]KRT]UJUU˫VVJ껪UUTUjRkZ]JtZUrWKURBUJUZ]J]W+RUUUUZ\JUUUUVҪUԺU)KRUUZuUH*U*UV]UUUK\꺖UJUUU*VUUTUjUUkUUUVҪTUUUUURU*RkUU]U]\TRURUJuWJU]UR*TʪUU.RVUTWU\]UUJU.T]UURuUR++ʗUUUʪUUU*HZꮪ]WUkꪪZ]UUԫڪR֪UVֹw-u]+UU*UUԪUԪ*W+UZtWWKUUUUWR\WJUWU*WWU.]*֪WUUR#W]UTuUU]UUWJҪU]UTZ]WUUUUUUUUUjUUUTT.˕UTUkU.UUJVW*]UU]RU]VU]*RKUU.UԮ*Tk]rȪ뫕uUUU]UTU˗RUURWU]Ju.UUR˭UTR)tUUKʪZU+UUZU"몪uҮꪩrꪺUUKUW.WU.UUU-UT]UUUuRURuKVU˩Vu.T*UjUVu*ZKUuUUUJURURUkUuVUuUkZ]UUU"WUWUUU]]UUv.]*jURUZ*KUKUjTUUU\ZUT]ZҪRW]JJVuWKTUU*URU-uTuU*ʪUUW*ꪩK]UU-U*ꫪU-u*ꮪ]UUjU]\u-UjRUUUUUURUk]-RUURkUrUUKU.U]+VUJ*u].]RUUU]]WUUt*ҺUUUUJWVWVUUjJU֭jtwZK]U]UK+JURkUHUVKUծ*WUUʪKW.UUUUuKUUUu.WUu\W+THUUjUWUUR-UUURWU-WJUrTJFԪUU˪tUJUZRRKu]\H \ No newline at end of file diff --git a/tests/test_fileref.cpp b/tests/test_fileref.cpp index 2220a4ed..3d38e5e4 100644 --- a/tests/test_fileref.cpp +++ b/tests/test_fileref.cpp @@ -385,6 +385,7 @@ public: CPPUNIT_ASSERT(extensions.contains("dsf")); CPPUNIT_ASSERT(extensions.contains("dff")); CPPUNIT_ASSERT(extensions.contains("dsdiff")); + CPPUNIT_ASSERT(extensions.contains("shn")); } void testFileResolver() diff --git a/tests/test_shorten.cpp b/tests/test_shorten.cpp new file mode 100644 index 00000000..103b18be --- /dev/null +++ b/tests/test_shorten.cpp @@ -0,0 +1,44 @@ +#include +#include + +#include "tbytevectorlist.h" +#include "tpropertymap.h" +#include "tag.h" +#include "shortenfile.h" +#include "plainfile.h" +#include +#include "utils.h" + +using namespace std; +using namespace TagLib; + +class TestShorten : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestShorten); + CPPUNIT_TEST(testBasic); + CPPUNIT_TEST(testTags); + CPPUNIT_TEST_SUITE_END(); + +public: + + void testBasic() + { + Shorten::File f(TEST_FILE_PATH_C("2sec-silence.shn")); + CPPUNIT_ASSERT(f.audioProperties()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->lengthInSeconds()); + CPPUNIT_ASSERT_EQUAL(2000, f.audioProperties()->lengthInMilliseconds()); + CPPUNIT_ASSERT_EQUAL(1411, f.audioProperties()->bitrate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels()); + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->shortenVersion()); + CPPUNIT_ASSERT_EQUAL(5, f.audioProperties()->fileType()); + CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample()); + CPPUNIT_ASSERT_EQUAL(static_cast(88200), f.audioProperties()->sampleFrames()); + } + + void testTags() + { + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestShorten); diff --git a/tests/test_sizes.cpp b/tests/test_sizes.cpp index c561db03..5c2b2e9a 100644 --- a/tests/test_sizes.cpp +++ b/tests/test_sizes.cpp @@ -92,6 +92,9 @@ #include "rifffile.h" #include "s3mfile.h" #include "s3mproperties.h" +#include "shortenfile.h" +#include "shortenproperties.h" +#include "shortentag.h" #include "speexfile.h" #include "speexproperties.h" #include "synchronizedlyricsframe.h" @@ -245,6 +248,9 @@ public: CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::RIFF::WAV::Properties)); CPPUNIT_ASSERT_EQUAL(classSize(2, true), sizeof(TagLib::S3M::File)); CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::S3M::Properties)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Shorten::File)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Shorten::Properties)); + CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Shorten::Tag)); CPPUNIT_ASSERT_EQUAL(classSize(1, false), sizeof(TagLib::String)); CPPUNIT_ASSERT_EQUAL(classSize(2, false), sizeof(TagLib::StringList)); CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Tag));