diff options
author | A. Cody Schuffelen <schuffelen@google.com> | 2020-11-20 02:19:38 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-11-20 02:19:38 +0000 |
commit | 4534b4c172b259218a40c40fdc235b46843037b8 (patch) | |
tree | 773e0b92a05045d70173dcf2aa2351c1fb1a96d0 | |
parent | b0487c6a476c4e43c8f187117d454ac3aa81a5d5 (diff) | |
parent | 739708406bbb5699d11c6e9b742cf5beb3af954c (diff) | |
download | libwebm-4534b4c172b259218a40c40fdc235b46843037b8.tar.gz |
Merge remote-tracking branch 'origin/upstream-master' am: 2efb1cd57a am: 739708406b
Original change: https://android-review.googlesource.com/c/platform/external/libwebm/+/1504154
Change-Id: I613fd1293bc638f7553b76ca4dc721267fc66f41
892 files changed, 51781 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7ce4e92 --- /dev/null +++ b/.clang-format @@ -0,0 +1,152 @@ +--- +Language: Cpp +# BasedOnStyle: Google +# Generated with clang-format 7.0.1 +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^<ext/.*\.h>' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2b5cda5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +*.sln eol=crlf +*.vcproj eol=crlf +*.vsprops eol=crlf +*.vcxproj eol=crlf +*.mkv -text -diff +*.webm -text -diff diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2abbaa6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +*.mkv +*.MKV +core +*.a +*.d +*.so* +*.o +*~ +*.swp +*.ncb +*.user +*.suo +*.exe +/*.webm +Debug +Release +*.sdf +*.opensdf +ipch +dumpvtt +mkvmuxer_sample +mkvparser_sample +vttdemux +mkvmuxer_tests +mkvparser_tests +webm_info +webm2pes +webm2pes_tests +webm2ts +vp9_header_parser_tests +vp9_level_stats_tests +Makefile +CMakeFiles +CMakeCache.txt +*.cmake diff --git a/AUTHORS.TXT b/AUTHORS.TXT new file mode 100644 index 0000000..9686ac1 --- /dev/null +++ b/AUTHORS.TXT @@ -0,0 +1,4 @@ +# Names should be added to this file like so: +# Name or Organization <email address> + +Google Inc. diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..b46ba10 --- /dev/null +++ b/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE:= libwebm +LOCAL_CPPFLAGS:=-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS +LOCAL_CPPFLAGS+=-D__STDC_LIMIT_MACROS -std=c++11 +LOCAL_C_INCLUDES:= $(LOCAL_PATH) +LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH) + +LOCAL_SRC_FILES:= common/file_util.cc \ + common/hdr_util.cc \ + mkvparser/mkvparser.cc \ + mkvparser/mkvreader.cc \ + mkvmuxer/mkvmuxer.cc \ + mkvmuxer/mkvmuxerutil.cc \ + mkvmuxer/mkvwriter.cc +include $(BUILD_STATIC_LIBRARY) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9fa5a53 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,452 @@ +## Copyright (c) 2015 The WebM project authors. All Rights Reserved. +## +## Use of this source code is governed by a BSD-style license +## that can be found in the LICENSE file in the root of the source +## tree. An additional intellectual property rights grant can be found +## in the file PATENTS. All contributing project authors may +## be found in the AUTHORS file in the root of the source tree. +cmake_minimum_required(VERSION 3.2) +project(LIBWEBM CXX) + +include(GNUInstallDirs) +include("${CMAKE_CURRENT_SOURCE_DIR}/build/cxx_flags.cmake") + +if (NOT BUILD_SHARED_LIBS) + include("${CMAKE_CURRENT_SOURCE_DIR}/build/msvc_runtime.cmake") +endif () + +set(LIBWEBM_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + +# Build/test configuration flags. +option(ENABLE_WEBMTS "Enables WebM PES/TS support." ON) +option(ENABLE_WEBMINFO "Enables building webm_info." ON) +option(ENABLE_TESTS "Enables tests." OFF) +option(ENABLE_IWYU "Enables include-what-you-use support." OFF) +option(ENABLE_WERROR "Enable warnings as errors." OFF) +option(ENABLE_WEBM_PARSER "Enables new parser API." OFF) + +if(WIN32) + require_cxx_flag_nomsvc("-std=gnu++11") +else() + require_cxx_flag_nomsvc("-std=c++11") +endif() + +add_cxx_preproc_definition("__STDC_CONSTANT_MACROS") +add_cxx_preproc_definition("__STDC_FORMAT_MACROS") +add_cxx_preproc_definition("__STDC_LIMIT_MACROS") + +# Set up compiler flags and build properties. +include_directories("${LIBWEBM_SRC_DIR}") + +if (MSVC) + add_cxx_flag_if_supported("/W4") + # Disable MSVC warnings that suggest making code non-portable. + add_cxx_flag_if_supported("/wd4996") + if (ENABLE_WERROR) + add_cxx_flag_if_supported("/WX") + endif () +else () + add_cxx_flag_if_supported("-Wall") + add_cxx_flag_if_supported("-Wextra") + add_cxx_flag_if_supported("-Wnarrowing") + add_cxx_flag_if_supported("-Wno-deprecated") + add_cxx_flag_if_supported("-Wshorten-64-to-32") + if (ENABLE_WERROR) + add_cxx_flag_if_supported("-Werror") + endif () +endif () + +# Source list variables. +set(dumpvtt_sources "${LIBWEBM_SRC_DIR}/dumpvtt.cc") + +set(libwebm_common_sources + "${LIBWEBM_SRC_DIR}/common/file_util.cc" + "${LIBWEBM_SRC_DIR}/common/file_util.h" + "${LIBWEBM_SRC_DIR}/common/hdr_util.cc" + "${LIBWEBM_SRC_DIR}/common/hdr_util.h" + "${LIBWEBM_SRC_DIR}/common/webmids.h") + +set(mkvmuxer_sources + "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxer.cc" + "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxer.h" + "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxertypes.h" + "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxerutil.cc" + "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxerutil.h" + "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvwriter.cc" + "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvwriter.h" + "${LIBWEBM_SRC_DIR}/common/webmids.h") + +set(mkvmuxer_sample_sources + "${LIBWEBM_SRC_DIR}/mkvmuxer_sample.cc" + "${LIBWEBM_SRC_DIR}/sample_muxer_metadata.cc" + "${LIBWEBM_SRC_DIR}/sample_muxer_metadata.h") + +set(mkvmuxer_tests_sources + "${LIBWEBM_SRC_DIR}/testing/mkvmuxer_tests.cc" + "${LIBWEBM_SRC_DIR}/testing/test_util.cc" + "${LIBWEBM_SRC_DIR}/testing/test_util.h") + +set(mkvparser_sources + "${LIBWEBM_SRC_DIR}/mkvparser/mkvparser.cc" + "${LIBWEBM_SRC_DIR}/mkvparser/mkvparser.h" + "${LIBWEBM_SRC_DIR}/mkvparser/mkvreader.cc" + "${LIBWEBM_SRC_DIR}/mkvparser/mkvreader.h" + "${LIBWEBM_SRC_DIR}/common/webmids.h") + +set(mkvparser_sample_sources "${LIBWEBM_SRC_DIR}/mkvparser_sample.cc") +set(mkvparser_tests_sources + "${LIBWEBM_SRC_DIR}/testing/mkvparser_tests.cc" + "${LIBWEBM_SRC_DIR}/testing/test_util.cc" + "${LIBWEBM_SRC_DIR}/testing/test_util.h") + +set(vp9_header_parser_tests_sources + "${LIBWEBM_SRC_DIR}/common/vp9_header_parser_tests.cc" + "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.cc" + "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.h" + "${LIBWEBM_SRC_DIR}/testing/test_util.cc" + "${LIBWEBM_SRC_DIR}/testing/test_util.h") + +set(vp9_level_stats_tests_sources + "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.cc" + "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.h" + "${LIBWEBM_SRC_DIR}/common/vp9_level_stats_tests.cc" + "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.cc" + "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.h" + "${LIBWEBM_SRC_DIR}/testing/test_util.cc" + "${LIBWEBM_SRC_DIR}/testing/test_util.h") + +set(vttdemux_sources + "${LIBWEBM_SRC_DIR}/vttdemux.cc" + "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.cc" + "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.h") + +set(webm_parser_public_headers + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/buffer_reader.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/callback.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/dom_types.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/element.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/file_reader.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/id.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/istream_reader.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/reader.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/status.h" + "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/webm_parser.h") + +set(webm_parser_sources + ${webm_parser_public_headers} + "${LIBWEBM_SRC_DIR}/webm_parser/src/ancestory.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/ancestory.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/audio_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/bit_utils.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/bit_utils.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/block_additions_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/block_group_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/block_header_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/block_header_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/block_more_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/block_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/block_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/bool_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/buffer_reader.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/byte_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/callback.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/chapter_atom_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/chapter_display_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/chapters_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/cluster_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/colour_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/content_enc_aes_settings_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/content_encoding_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/content_encodings_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/content_encryption_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/cue_point_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/cue_track_positions_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/cues_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/date_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/date_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/ebml_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/edition_entry_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/element_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/file_reader.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/float_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/float_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/id_element_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/id_element_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/id_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/id_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/info_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/int_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/istream_reader.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/master_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/master_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/master_value_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/mastering_metadata_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/parser_utils.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/parser_utils.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/projection_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/recursive_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/seek_head_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/seek_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/segment_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/segment_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/simple_tag_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/size_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/size_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/skip_callback.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/skip_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/skip_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/slices_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/tag_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/tags_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/targets_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/time_slice_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/track_entry_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/tracks_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/unknown_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/unknown_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/var_int_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/var_int_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/video_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/virtual_block_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/virtual_block_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/void_parser.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/src/void_parser.h" + "${LIBWEBM_SRC_DIR}/webm_parser/src/webm_parser.cc") + +set(webm_parser_demo_sources "${LIBWEBM_SRC_DIR}/webm_parser/demo/demo.cc") +set(webm_parser_tests_sources + "${LIBWEBM_SRC_DIR}/webm_parser/tests/audio_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/bit_utils_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_additions_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_group_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_header_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_more_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/bool_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/buffer_reader_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/byte_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/callback_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/chapter_atom_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/chapter_display_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/chapters_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/cluster_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/colour_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_enc_aes_settings_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_encoding_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_encodings_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_encryption_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/cue_point_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/cue_track_positions_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/cues_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/date_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/ebml_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/edition_entry_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/element_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/float_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/id_element_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/id_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/info_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/int_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/istream_reader_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/limited_reader_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/master_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/master_value_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/mastering_metadata_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/parser_utils_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/projection_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/recursive_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/seek_head_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/seek_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/segment_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/simple_tag_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/size_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/skip_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/slices_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/tag_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/tags_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/targets_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/time_slice_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/track_entry_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/tracks_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/unknown_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/var_int_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/video_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/virtual_block_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/void_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/webm_parser_test.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/element_parser_test.h" + "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/limited_reader.cc" + "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/limited_reader.h" + "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/mock_callback.h" + "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/parser_test.h" + "${LIBWEBM_SRC_DIR}/webm_parser/tests/webm_parser_tests.cc") + +set(webm_info_sources + "${LIBWEBM_SRC_DIR}/common/indent.cc" + "${LIBWEBM_SRC_DIR}/common/indent.h" + "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.cc" + "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.h" + "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.cc" + "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.h" + "${LIBWEBM_SRC_DIR}/common/webm_constants.h" + "${LIBWEBM_SRC_DIR}/common/webm_endian.cc" + "${LIBWEBM_SRC_DIR}/common/webm_endian.h" + "${LIBWEBM_SRC_DIR}/webm_info.cc") + +set(webmts_sources + "${LIBWEBM_SRC_DIR}/common/libwebm_util.cc" + "${LIBWEBM_SRC_DIR}/common/libwebm_util.h" + "${LIBWEBM_SRC_DIR}/common/video_frame.cc" + "${LIBWEBM_SRC_DIR}/common/video_frame.h" + "${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts.cc" + "${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts.h" + "${LIBWEBM_SRC_DIR}/m2ts/vpxpes_parser.cc" + "${LIBWEBM_SRC_DIR}/m2ts/vpxpes_parser.h" + "${LIBWEBM_SRC_DIR}/m2ts/webm2pes.cc" + "${LIBWEBM_SRC_DIR}/m2ts/webm2pes.h") + +set(webm2pes_sources "${LIBWEBM_SRC_DIR}/m2ts/webm2pes_main.cc") +set(webm2pes_tests_sources + "${LIBWEBM_SRC_DIR}/testing/test_util.cc" + "${LIBWEBM_SRC_DIR}/testing/test_util.h" + "${LIBWEBM_SRC_DIR}/testing/video_frame_tests.cc" + "${LIBWEBM_SRC_DIR}/m2ts/tests/webm2pes_tests.cc") +set(webm2ts_sources "${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts_main.cc") + +set(webvtt_common_sources + "${LIBWEBM_SRC_DIR}/webvtt/vttreader.cc" + "${LIBWEBM_SRC_DIR}/webvtt/vttreader.h" + "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.cc" + "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.h") + +# Targets. +add_library(mkvmuxer OBJECT ${mkvmuxer_sources}) +add_library(mkvparser OBJECT ${mkvparser_sources}) +add_library(webvtt_common OBJECT ${webvtt_common_sources}) + +add_library(webm ${libwebm_common_sources} + $<TARGET_OBJECTS:mkvmuxer> + $<TARGET_OBJECTS:mkvparser>) + +if (WIN32) + # Use libwebm and libwebm.lib for project and library name on Windows (instead + # webm and webm.lib). + set_target_properties(webm PROPERTIES PROJECT_LABEL libwebm) + set_target_properties(webm PROPERTIES PREFIX lib) +endif () + +add_executable(mkvparser_sample ${mkvparser_sample_sources}) +target_link_libraries(mkvparser_sample LINK_PUBLIC webm) + +add_executable(mkvmuxer_sample ${mkvmuxer_sample_sources} + $<TARGET_OBJECTS:webvtt_common>) +target_link_libraries(mkvmuxer_sample LINK_PUBLIC webm) + +add_executable(dumpvtt ${dumpvtt_sources} $<TARGET_OBJECTS:webvtt_common>) +target_link_libraries(dumpvtt LINK_PUBLIC webm) + +add_executable(vttdemux ${vttdemux_sources}) +target_link_libraries(vttdemux LINK_PUBLIC webm) + +if (ENABLE_WEBMINFO) + add_executable(webm_info ${webm_info_sources}) + target_link_libraries(webm_info LINK_PUBLIC webm) +endif () + +if (ENABLE_WEBM_PARSER) + include_directories(webm_parser webm_parser/include) + add_library(webm_parser OBJECT ${webm_parser_sources}) + target_sources(webm PUBLIC $<TARGET_OBJECTS:webm_parser>) + set_target_properties(webm PROPERTIES PUBLIC_HEADER + "${webm_parser_public_headers}") + + add_executable(webm_parser_demo ${webm_parser_demo_sources}) + target_link_libraries(webm_parser_demo LINK_PUBLIC webm) + + install(TARGETS webm + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/webm) +endif () + +if (ENABLE_WEBMTS) + add_library(webmts OBJECT ${webmts_sources}) + + add_executable(webm2pes ${webm2pes_sources} $<TARGET_OBJECTS:webmts>) + target_link_libraries(webm2pes LINK_PUBLIC webm) + + add_executable(webm2ts ${webm2ts_sources} $<TARGET_OBJECTS:webmts>) + target_link_libraries(webm2ts LINK_PUBLIC webm) +endif () + +if (ENABLE_TESTS) + set(GTEST_SRC_DIR "${LIBWEBM_SRC_DIR}/../googletest" CACHE PATH + "Path to Googletest git repository.") + # This directory is where libwebm will build googletest dependencies. + set(GTEST_BUILD_DIR "${CMAKE_BINARY_DIR}/googletest_build") + + if (LIBWEBM_DISABLE_GTEST_CMAKE) + add_library(gtest STATIC "${GTEST_SRC_DIR}/googletest/src/gtest-all.cc") + include_directories("${GTEST_SRC_DIR}/googletest") + else () + add_subdirectory("${GTEST_SRC_DIR}" "${GTEST_BUILD_DIR}") + endif () + include_directories("${GTEST_SRC_DIR}/googletest/include") + + add_executable(mkvmuxer_tests ${mkvmuxer_tests_sources}) + target_link_libraries(mkvmuxer_tests LINK_PUBLIC gtest webm) + + add_executable(mkvparser_tests ${mkvparser_tests_sources}) + target_link_libraries(mkvparser_tests LINK_PUBLIC gtest webm) + + add_executable(vp9_header_parser_tests ${vp9_header_parser_tests_sources}) + target_link_libraries(vp9_header_parser_tests LINK_PUBLIC gtest webm) + + add_executable(vp9_level_stats_tests ${vp9_level_stats_tests_sources}) + target_link_libraries(vp9_level_stats_tests LINK_PUBLIC gtest webm) + + if (ENABLE_WEBMTS) + add_executable(webm2pes_tests ${webm2pes_tests_sources} + $<TARGET_OBJECTS:webmts>) + target_link_libraries(webm2pes_tests LINK_PUBLIC gtest webm) + endif () + + if (ENABLE_WEBM_PARSER) + include_directories("${GTEST_SRC_DIR}/googlemock/include") + add_executable(webm_parser_tests ${webm_parser_tests_sources}) + target_link_libraries(webm_parser_tests LINK_PUBLIC gmock gtest webm) + endif () +endif () + +# Include-what-you-use. +if (ENABLE_IWYU) + # Make sure all the tools necessary for IWYU are present. + find_program(iwyu_path NAMES include-what-you-use) + find_program(iwyu_tool_path NAMES iwyu_tool.py) + + # Some odd behavior on cmake's part: PYTHON_EXECUTABLE and PYTHON_VERSION_* + # are set by cmake when it does its internal python check, but + # PYTHONINTERP_FOUND is empty without explicitly looking for it. + find_package(PythonInterp) + + if (iwyu_path AND iwyu_tool_path AND PYTHONINTERP_FOUND) + # Enable compilation command export (needed for iwyu_tool.py) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + + # Add a custom target to run iwyu across all targets. + add_custom_target(iwyu + ALL + COMMAND "${PYTHON_EXECUTABLE}" "${iwyu_tool_path}" -p + "${CMAKE_BINARY_DIR}" + COMMENT "Running include-what-you-use..." + VERBATIM) + else () + message(STATUS "Ignoring ENABLE_IWYU because reasons:") + message(STATUS " iwyu_path=" ${iwyu_path}) + message(STATUS " iwyu_tool_path=" ${iwyu_tool_path}) + message(STATUS " PYTHONINTERP_FOUND=" ${PYTHONINTERP_FOUND}) + message(STATUS " See README.libwebm for more information.") + endif () +endif () diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 0000000..7a6f995 --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,30 @@ +Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Makefile.unix b/Makefile.unix new file mode 100644 index 0000000..0430d17 --- /dev/null +++ b/Makefile.unix @@ -0,0 +1,57 @@ +CXX := g++ +DEFINES := -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS +DEFINES += -D__STDC_LIMIT_MACROS +INCLUDES := -I. +CXXFLAGS := -W -Wall -g -std=c++11 +ALL_CXXFLAGS := -MMD -MP $(DEFINES) $(INCLUDES) $(CXXFLAGS) +LIBWEBMA := libwebm.a +LIBWEBMSO := libwebm.so +WEBMOBJS := mkvmuxer/mkvmuxer.o mkvmuxer/mkvmuxerutil.o mkvmuxer/mkvwriter.o +WEBMOBJS += mkvparser/mkvparser.o mkvparser/mkvreader.o +WEBMOBJS += common/file_util.o common/hdr_util.o +OBJSA := $(WEBMOBJS:.o=_a.o) +OBJSSO := $(WEBMOBJS:.o=_so.o) +VTTOBJS := webvtt/vttreader.o webvtt/webvttparser.o sample_muxer_metadata.o +EXEOBJS := mkvmuxer_sample.o mkvparser_sample.o dumpvtt.o vttdemux.o +EXES := mkvparser_sample mkvmuxer_sample dumpvtt vttdemux +DEPS := $(WEBMOBJS:.o=.d) $(OBJECTS1:.o=.d) $(OBJECTS2:.o=.d) +DEPS += $(OBJECTS3:.o=.d) $(OBJECTS4:.o=.d) $(OBJSA:.o=.d) $(OBJSSO:.o=.d) +DEPS += $(VTTOBJS:.o=.d) $(EXEOBJS:.o=.d) +CLEAN := $(EXEOBJS) $(VTTOBJS) $(WEBMOBJS) $(OBJSA) $(OBJSSO) $(LIBWEBMA) +CLEAN += $(LIBWEBMSO) $(EXES) $(DEPS) $(INFOOBJS) + +all: $(EXES) + +mkvparser_sample: mkvparser_sample.o $(LIBWEBMA) + $(CXX) $^ -o $@ + +mkvmuxer_sample: mkvmuxer_sample.o $(VTTOBJS) $(LIBWEBMA) + $(CXX) $^ -o $@ + +dumpvtt: dumpvtt.o $(VTTOBJS) $(WEBMOBJS) + $(CXX) $^ -o $@ + +vttdemux: vttdemux.o $(VTTOBJS) $(LIBWEBMA) + $(CXX) $^ -o $@ + +shared: $(LIBWEBMSO) + +libwebm.a: $(OBJSA) + $(AR) rcs $@ $^ + +libwebm.so: $(OBJSSO) + $(CXX) $(ALL_CXXFLAGS) -shared $(OBJSSO) -o $(LIBWEBMSO) + +%.o: %.cc + $(CXX) -c $(ALL_CXXFLAGS) $< -o $@ +%_a.o: %.cc + $(CXX) -c $(ALL_CXXFLAGS) $< -o $@ +%_so.o: %.cc + $(CXX) -c $(ALL_CXXFLAGS) -fPIC $< -o $@ + +clean: + $(RM) -f $(CLEAN) Makefile.bak + +ifneq ($(MAKECMDGOALS), clean) + -include $(DEPS) +endif diff --git a/PATENTS.TXT b/PATENTS.TXT new file mode 100644 index 0000000..caedf60 --- /dev/null +++ b/PATENTS.TXT @@ -0,0 +1,23 @@ +Additional IP Rights Grant (Patents) +------------------------------------ + +"These implementations" means the copyrightable works that implement the WebM +codecs distributed by Google as part of the WebM Project. + +Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable (except as stated in this section) patent license to +make, have made, use, offer to sell, sell, import, transfer, and otherwise +run, modify and propagate the contents of these implementations of WebM, where +such license applies only to those patent claims, both currently owned by +Google and acquired in the future, licensable by Google that are necessarily +infringed by these implementations of WebM. This grant does not include claims +that would be infringed only as a consequence of further modification of these +implementations. If you or your agent or exclusive licensee institute or order +or agree to the institution of patent litigation or any other patent +enforcement activity against any entity (including a cross-claim or +counterclaim in a lawsuit) alleging that any of these implementations of WebM +or any code incorporated within any of these implementations of WebM +constitute direct or contributory patent infringement, or inducement of +patent infringement, then any patent rights granted to you under this License +for these implementations of WebM shall terminate as of the date such +litigation is filed. diff --git a/README.libwebm b/README.libwebm new file mode 100644 index 0000000..3406f80 --- /dev/null +++ b/README.libwebm @@ -0,0 +1,143 @@ +Building Libwebm + +To build libwebm you must first create project files. To do this run cmake +and pass it the path to your libwebm repo. + +Makefile.unix can be used as a fallback on systems that cmake does not +support. + + +CMake Basics + +To generate project/make files for the default toolchain on your system simply +run cmake with the path to the libwebm repo: + +$ cmake path/to/libwebm + +On Windows the above command will produce Visual Studio project files for the +newest Visual Studio detected on the system. On Mac OS X and Linux systems, the +above command will produce a makefile. + +To control what types of projects are generated the -G parameter is added to +the cmake command line. This argument must be followed by the name of a +generator. Running cmake with the --help argument will list the available +generators for your system. + +On Mac OS X you would run the following command to generate Xcode projects: + +$ cmake path/to/libwebm -G Xcode + +On a Windows box you would run the following command to generate Visual Studio +2013 projects: + +$ cmake path/to/libwebm -G "Visual Studio 12" + +To generate 64-bit Windows Visual Studio 2013 projects: + +$ cmake path/to/libwebm "Visual Studio 12 Win64" + + +CMake Makefiles: Debugging and Optimization + +Unlike Visual Studio and Xcode projects, the build configuration for make builds +is controlled when you run cmake. The following examples demonstrate various +build configurations. + +Omitting the build type produces makefiles that use build flags containing +neither optimization nor debug flags: +$ cmake path/to/libwebm + +A makefile using release (optimized) flags is produced like this: +$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=release + +A release build with debug info can be produced as well: +$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=relwithdebinfo + +And your standard debug build will be produced using: +$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=debug + + +Tests + +To enable libwebm tests add -DENABLE_TESTS=ON CMake generation command line. For +example: + +$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON + +Libwebm tests depend on googletest. By default googletest is expected to be a +sibling directory of the Libwebm repository. To change that, update your CMake +command to be similar to the following: + +$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON \ + -DGTEST_SRC_DIR=/path/to/googletest + +The tests rely upon the LIBWEBM_TEST_DATA_PATH environment variable to locate +test input. The following example demonstrates running the muxer tests from the +build directory: + +$ LIBWEBM_TEST_DATA_PATH=path/to/libwebm/testing/testdata ./mkvmuxer_tests + +Note: Libwebm Googletest integration was built with googletest from + https://github.com/google/googletest.git at git revision + ddb8012eb48bc203aa93dcc2b22c1db516302b29. + + +CMake Include-what-you-use integration + +Include-what-you-use is an analysis tool that helps ensure libwebm includes the +C/C++ header files actually in use. To enable the integration support +ENABLE_IWYU must be turned on at cmake run time: + +$ cmake path/to/libwebm -G "Unix Makefiles" -DENABLE_IWYU=ON + +This adds the iwyu target to the build. To run include-what-you-use: + +$ make iwyu + +The following requirements must be met for ENABLE_IWYU to enable the iwyu +target: + +1. include-what-you-use and iwyu_tool.py must be in your PATH. +2. A python interpreter must be on the system and available to CMake. + +The values of the following variables are used to determine if the requirements +have been met. Values to the right of the equals sign are what a successful run +might look like: + iwyu_path=/path/to/iwyu_tool.py + iwyu_tool_path=/path/to/include-what-you-use + PYTHONINTERP_FOUND=TRUE + +An empty PYTHONINTERP_FOUND, or iwyu_path/iwyu_tool_path suffixed with NOTFOUND +are failures. + +For Include-what-you-use setup instructions, see: +https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/InstructionsForUsers.md + +If, when building the iwyu target, compile errors reporting failures loading +standard include files occur, one solution can be found here: +https://github.com/include-what-you-use/include-what-you-use/issues/100 + + +CMake cross compile +To cross compile libwebm for Windows using mingw-w64 run cmake with the +following arguments: + +$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \ + path/to/libwebm + +Note1: As of this writing googletest will not build via mingw-w64 without +disabling pthreads. +googletest hash: d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0 + +To build with tests when using mingw-w64 use the following arguments when +running CMake: + +$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \ + -DENABLE_TESTS=ON -Dgtest_disable_pthreads=ON path/to/libwebm + +Note2: i686-w64-mingw32 is the default compiler. This can be controlled using +the MINGW_PREFIX variable: + +$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \ + -DMINGW_PREFIX=x86_64-w64-mingw32 path/to/libwebm + diff --git a/build/cxx_flags.cmake b/build/cxx_flags.cmake new file mode 100644 index 0000000..9e96889 --- /dev/null +++ b/build/cxx_flags.cmake @@ -0,0 +1,73 @@ +## Copyright (c) 2016 The WebM project authors. All Rights Reserved. +## +## Use of this source code is governed by a BSD-style license +## that can be found in the LICENSE file in the root of the source +## tree. An additional intellectual property rights grant can be found +## in the file PATENTS. All contributing project authors may +## be found in the AUTHORS file in the root of the source tree. +cmake_minimum_required(VERSION 3.2) + +include(CheckCXXCompilerFlag) + +# String used to cache failed CXX flags. +set(LIBWEBM_FAILED_CXX_FLAGS) + +# Checks C++ compiler for support of $cxx_flag. Adds $cxx_flag to +# $CMAKE_CXX_FLAGS when the compile test passes. Caches $c_flag in +# $LIBWEBM_FAILED_CXX_FLAGS when the test fails. +function (add_cxx_flag_if_supported cxx_flag) + unset(CXX_FLAG_FOUND CACHE) + string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND) + unset(CXX_FLAG_FAILED CACHE) + string(FIND "${LIBWEBM_FAILED_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FAILED) + + if (${CXX_FLAG_FOUND} EQUAL -1 AND ${CXX_FLAG_FAILED} EQUAL -1) + unset(CXX_FLAG_SUPPORTED CACHE) + message("Checking CXX compiler flag support for: " ${cxx_flag}) + check_cxx_compiler_flag("${cxx_flag}" CXX_FLAG_SUPPORTED) + if (CXX_FLAG_SUPPORTED) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${cxx_flag}" CACHE STRING "" + FORCE) + else () + set(LIBWEBM_FAILED_CXX_FLAGS "${LIBWEBM_FAILED_CXX_FLAGS} ${cxx_flag}" + CACHE STRING "" FORCE) + endif () + endif () +endfunction () + +# Checks CXX compiler for support of $cxx_flag and terminates generation when +# support is not present. +function (require_cxx_flag cxx_flag) + unset(CXX_FLAG_FOUND CACHE) + string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND) + + if (${CXX_FLAG_FOUND} EQUAL -1) + unset(LIBWEBM_HAVE_CXX_FLAG CACHE) + message("Checking CXX compiler flag support for: " ${cxx_flag}) + check_cxx_compiler_flag("${cxx_flag}" LIBWEBM_HAVE_CXX_FLAG) + if (NOT LIBWEBM_HAVE_CXX_FLAG) + message(FATAL_ERROR + "${PROJECT_NAME} requires support for CXX flag: ${cxx_flag}.") + endif () + set(CMAKE_CXX_FLAGS "${cxx_flag} ${CMAKE_CXX_FLAGS}" CACHE STRING "" FORCE) + endif () +endfunction () + +# Checks only non-MSVC targets for support of $cxx_flag. +function (require_cxx_flag_nomsvc cxx_flag) + if (NOT MSVC) + require_cxx_flag(${cxx_flag}) + endif () +endfunction () + +# Adds $preproc_def to CXX compiler command line (as -D$preproc_def) if not +# already present. +function (add_cxx_preproc_definition preproc_def) + unset(PREPROC_DEF_FOUND CACHE) + string(FIND "${CMAKE_CXX_FLAGS}" "${preproc_def}" PREPROC_DEF_FOUND) + + if (${PREPROC_DEF_FOUND} EQUAL -1) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D${preproc_def}" CACHE STRING "" + FORCE) + endif () +endfunction () diff --git a/build/msvc_runtime.cmake b/build/msvc_runtime.cmake new file mode 100644 index 0000000..d7d7add --- /dev/null +++ b/build/msvc_runtime.cmake @@ -0,0 +1,23 @@ +## Copyright (c) 2015 The WebM project authors. All Rights Reserved. +## +## Use of this source code is governed by a BSD-style license +## that can be found in the LICENSE file in the root of the source +## tree. An additional intellectual property rights grant can be found +## in the file PATENTS. All contributing project authors may +## be found in the AUTHORS file in the root of the source tree. +cmake_minimum_required(VERSION 2.8) + +if (MSVC) + # CMake defaults to producing code linked to the DLL MSVC runtime. In libwebm + # static is typically desired. Force static code generation unless the user + # running CMake set MSVC_RUNTIME to dll. + if (NOT "${MSVC_RUNTIME}" STREQUAL "dll") + foreach (flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if (${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif (${flag_var} MATCHES "/MD") + endforeach (flag_var) + endif () +endif () diff --git a/build/x86-mingw-gcc.cmake b/build/x86-mingw-gcc.cmake new file mode 100644 index 0000000..8416eaf --- /dev/null +++ b/build/x86-mingw-gcc.cmake @@ -0,0 +1,26 @@ +## Copyright (c) 2017 The WebM project authors. All Rights Reserved. +## +## Use of this source code is governed by a BSD-style license +## that can be found in the LICENSE file in the root of the source +## tree. An additional intellectual property rights grant can be found +## in the file PATENTS. All contributing project authors may +## be found in the AUTHORS file in the root of the source tree. +if (NOT LIBWEBM_BUILD_X86_MINGW_GCC_CMAKE_) +set(LIBWEBM_BUILD_X86_MINGW_GCC_CMAKE_ 1) + +set(CMAKE_SYSTEM_PROCESSOR "x86") +set(CMAKE_SYSTEM_NAME "Windows") +set(CMAKE_C_COMPILER_ARG1 "-m32") +set(CMAKE_CXX_COMPILER_ARG1 "-m32") + +if ("${CROSS}" STREQUAL "") + set(CROSS i686-w64-mingw32-) +endif () + +set(CMAKE_C_COMPILER ${CROSS}gcc) +set(CMAKE_CXX_COMPILER ${CROSS}g++) + +# Disable googletest CMake usage for mingw cross compiles. +set(LIBWEBM_DISABLE_GTEST_CMAKE 1) + +endif () # LIBWEBM_BUILD_X86_MINGW_GCC_CMAKE_ diff --git a/build/x86_64-mingw-gcc.cmake b/build/x86_64-mingw-gcc.cmake new file mode 100644 index 0000000..9db28b7 --- /dev/null +++ b/build/x86_64-mingw-gcc.cmake @@ -0,0 +1,24 @@ +## Copyright (c) 2017 The WebM project authors. All Rights Reserved. +## +## Use of this source code is governed by a BSD-style license +## that can be found in the LICENSE file in the root of the source +## tree. An additional intellectual property rights grant can be found +## in the file PATENTS. All contributing project authors may +## be found in the AUTHORS file in the root of the source tree. +if (NOT LIBWEBM_BUILD_X86_64_MINGW_GCC_CMAKE_) +set(LIBWEBM_BUILD_X86_64_MINGW_GCC_CMAKE_ 1) + +set(CMAKE_SYSTEM_PROCESSOR "x86_64") +set(CMAKE_SYSTEM_NAME "Windows") + +if ("${CROSS}" STREQUAL "") + set(CROSS x86_64-w64-mingw32-) +endif () + +set(CMAKE_C_COMPILER ${CROSS}gcc) +set(CMAKE_CXX_COMPILER ${CROSS}g++) + +# Disable googletest CMake usage for mingw cross compiles. +set(LIBWEBM_DISABLE_GTEST_CMAKE 1) + +endif () # LIBWEBM_BUILD_X86_64_MINGW_GCC_CMAKE_ diff --git a/codereview.settings b/codereview.settings new file mode 100644 index 0000000..ccba2ee --- /dev/null +++ b/codereview.settings @@ -0,0 +1,4 @@ +# This file is used by git cl to get repository specific information. +GERRIT_HOST: True +CODE_REVIEW_SERVER: chromium-review.googlesource.com +GERRIT_SQUASH_UPLOADS: False diff --git a/common/common.sh b/common/common.sh new file mode 100644 index 0000000..49fec79 --- /dev/null +++ b/common/common.sh @@ -0,0 +1,62 @@ +#!/bin/sh +## +## Copyright (c) 2015 The WebM project authors. All Rights Reserved. +## +## Use of this source code is governed by a BSD-style license +## that can be found in the LICENSE file in the root of the source +## tree. An additional intellectual property rights grant can be found +## in the file PATENTS. All contributing project authors may +## be found in the AUTHORS file in the root of the source tree. +## +set -e +devnull='> /dev/null 2>&1' + +readonly ORIG_PWD="$(pwd)" + +elog() { + echo "${0##*/} failed because: $@" 1>&2 +} + +vlog() { + if [ "${VERBOSE}" = "yes" ]; then + echo "$@" + fi +} + +# Terminates script when name of current directory does not match $1. +check_dir() { + current_dir="$(pwd)" + required_dir="$1" + if [ "${current_dir##*/}" != "${required_dir}" ]; then + elog "This script must be run from the ${required_dir} directory." + exit 1 + fi +} + +# Terminates the script when $1 is not in $PATH. Any arguments required for +# the tool being tested to return a successful exit code can be passed as +# additional arguments. +check_tool() { + tool="$1" + shift + tool_args="$@" + if ! eval "${tool}" ${tool_args} > /dev/null 2>&1; then + elog "${tool} must be in your path." + exit 1 + fi +} + +# Echoes git describe output for the directory specified by $1 to stdout. +git_describe() { + git_dir="$1" + check_git + echo $(git -C "${git_dir}" describe) +} + +# Echoes current git revision for the directory specifed by $1 to stdout. +git_revision() { + git_dir="$1" + check_git + echo $(git -C "${git_dir}" rev-parse HEAD) +} + diff --git a/common/file_util.cc b/common/file_util.cc new file mode 100644 index 0000000..6eb6428 --- /dev/null +++ b/common/file_util.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/file_util.h" + +#include <sys/stat.h> +#ifndef _MSC_VER +#include <unistd.h> // close() +#endif + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <ios> +#include <string> + +namespace libwebm { + +std::string GetTempFileName() { +#if !defined _MSC_VER && !defined __MINGW32__ + std::string temp_file_name_template_str = + std::string(std::getenv("TEST_TMPDIR") ? std::getenv("TEST_TMPDIR") + : ".") + + "/libwebm_temp.XXXXXX"; + char* temp_file_name_template = + new char[temp_file_name_template_str.length() + 1]; + memset(temp_file_name_template, 0, temp_file_name_template_str.length() + 1); + temp_file_name_template_str.copy(temp_file_name_template, + temp_file_name_template_str.length(), 0); + int fd = mkstemp(temp_file_name_template); + std::string temp_file_name = + (fd != -1) ? std::string(temp_file_name_template) : std::string(); + delete[] temp_file_name_template; + if (fd != -1) { + close(fd); + } + return temp_file_name; +#else + char tmp_file_name[_MAX_PATH]; +#if defined _MSC_VER || defined MINGW_HAS_SECURE_API + errno_t err = tmpnam_s(tmp_file_name); +#else + char* fname_pointer = tmpnam(tmp_file_name); + int err = (fname_pointer == &tmp_file_name[0]) ? 0 : -1; +#endif + if (err == 0) { + return std::string(tmp_file_name); + } + return std::string(); +#endif +} + +uint64_t GetFileSize(const std::string& file_name) { + uint64_t file_size = 0; +#ifndef _MSC_VER + struct stat st; + st.st_size = 0; + if (stat(file_name.c_str(), &st) == 0) { +#else + struct _stat st; + st.st_size = 0; + if (_stat(file_name.c_str(), &st) == 0) { +#endif + file_size = st.st_size; + } + return file_size; +} + +bool GetFileContents(const std::string& file_name, std::string* contents) { + std::ifstream file(file_name.c_str()); + *contents = std::string(static_cast<size_t>(GetFileSize(file_name)), 0); + if (file.good() && contents->size()) { + file.read(&(*contents)[0], contents->size()); + } + return !file.fail(); +} + +TempFileDeleter::TempFileDeleter() { file_name_ = GetTempFileName(); } + +TempFileDeleter::~TempFileDeleter() { + std::ifstream file(file_name_.c_str()); + if (file.good()) { + file.close(); + std::remove(file_name_.c_str()); + } +} + +} // namespace libwebm diff --git a/common/file_util.h b/common/file_util.h new file mode 100644 index 0000000..a873734 --- /dev/null +++ b/common/file_util.h @@ -0,0 +1,44 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_COMMON_FILE_UTIL_H_ +#define LIBWEBM_COMMON_FILE_UTIL_H_ + +#include <stdint.h> + +#include <string> + +#include "mkvmuxer/mkvmuxertypes.h" // LIBWEBM_DISALLOW_COPY_AND_ASSIGN() + +namespace libwebm { + +// Returns a temporary file name. +std::string GetTempFileName(); + +// Returns size of file specified by |file_name|, or 0 upon failure. +uint64_t GetFileSize(const std::string& file_name); + +// Gets the contents file_name as a string. Returns false on error. +bool GetFileContents(const std::string& file_name, std::string* contents); + +// Manages life of temporary file specified at time of construction. Deletes +// file upon destruction. +class TempFileDeleter { + public: + TempFileDeleter(); + explicit TempFileDeleter(std::string file_name) : file_name_(file_name) {} + ~TempFileDeleter(); + const std::string& name() const { return file_name_; } + + private: + std::string file_name_; + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(TempFileDeleter); +}; + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_FILE_UTIL_H_ diff --git a/common/hdr_util.cc b/common/hdr_util.cc new file mode 100644 index 0000000..916f717 --- /dev/null +++ b/common/hdr_util.cc @@ -0,0 +1,220 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "hdr_util.h" + +#include <climits> +#include <cstddef> +#include <new> + +#include "mkvparser/mkvparser.h" + +namespace libwebm { +const int Vp9CodecFeatures::kValueNotPresent = INT_MAX; + +bool CopyPrimaryChromaticity(const mkvparser::PrimaryChromaticity& parser_pc, + PrimaryChromaticityPtr* muxer_pc) { + muxer_pc->reset(new (std::nothrow) + mkvmuxer::PrimaryChromaticity(parser_pc.x, parser_pc.y)); + if (!muxer_pc->get()) + return false; + return true; +} + +bool MasteringMetadataValuePresent(double value) { + return value != mkvparser::MasteringMetadata::kValueNotPresent; +} + +bool CopyMasteringMetadata(const mkvparser::MasteringMetadata& parser_mm, + mkvmuxer::MasteringMetadata* muxer_mm) { + if (MasteringMetadataValuePresent(parser_mm.luminance_max)) + muxer_mm->set_luminance_max(parser_mm.luminance_max); + if (MasteringMetadataValuePresent(parser_mm.luminance_min)) + muxer_mm->set_luminance_min(parser_mm.luminance_min); + + PrimaryChromaticityPtr r_ptr(nullptr); + PrimaryChromaticityPtr g_ptr(nullptr); + PrimaryChromaticityPtr b_ptr(nullptr); + PrimaryChromaticityPtr wp_ptr(nullptr); + + if (parser_mm.r) { + if (!CopyPrimaryChromaticity(*parser_mm.r, &r_ptr)) + return false; + } + if (parser_mm.g) { + if (!CopyPrimaryChromaticity(*parser_mm.g, &g_ptr)) + return false; + } + if (parser_mm.b) { + if (!CopyPrimaryChromaticity(*parser_mm.b, &b_ptr)) + return false; + } + if (parser_mm.white_point) { + if (!CopyPrimaryChromaticity(*parser_mm.white_point, &wp_ptr)) + return false; + } + + if (!muxer_mm->SetChromaticity(r_ptr.get(), g_ptr.get(), b_ptr.get(), + wp_ptr.get())) { + return false; + } + + return true; +} + +bool ColourValuePresent(long long value) { + return value != mkvparser::Colour::kValueNotPresent; +} + +bool CopyColour(const mkvparser::Colour& parser_colour, + mkvmuxer::Colour* muxer_colour) { + if (!muxer_colour) + return false; + + if (ColourValuePresent(parser_colour.matrix_coefficients)) + muxer_colour->set_matrix_coefficients(parser_colour.matrix_coefficients); + if (ColourValuePresent(parser_colour.bits_per_channel)) + muxer_colour->set_bits_per_channel(parser_colour.bits_per_channel); + if (ColourValuePresent(parser_colour.chroma_subsampling_horz)) { + muxer_colour->set_chroma_subsampling_horz( + parser_colour.chroma_subsampling_horz); + } + if (ColourValuePresent(parser_colour.chroma_subsampling_vert)) { + muxer_colour->set_chroma_subsampling_vert( + parser_colour.chroma_subsampling_vert); + } + if (ColourValuePresent(parser_colour.cb_subsampling_horz)) + muxer_colour->set_cb_subsampling_horz(parser_colour.cb_subsampling_horz); + if (ColourValuePresent(parser_colour.cb_subsampling_vert)) + muxer_colour->set_cb_subsampling_vert(parser_colour.cb_subsampling_vert); + if (ColourValuePresent(parser_colour.chroma_siting_horz)) + muxer_colour->set_chroma_siting_horz(parser_colour.chroma_siting_horz); + if (ColourValuePresent(parser_colour.chroma_siting_vert)) + muxer_colour->set_chroma_siting_vert(parser_colour.chroma_siting_vert); + if (ColourValuePresent(parser_colour.range)) + muxer_colour->set_range(parser_colour.range); + if (ColourValuePresent(parser_colour.transfer_characteristics)) { + muxer_colour->set_transfer_characteristics( + parser_colour.transfer_characteristics); + } + if (ColourValuePresent(parser_colour.primaries)) + muxer_colour->set_primaries(parser_colour.primaries); + if (ColourValuePresent(parser_colour.max_cll)) + muxer_colour->set_max_cll(parser_colour.max_cll); + if (ColourValuePresent(parser_colour.max_fall)) + muxer_colour->set_max_fall(parser_colour.max_fall); + + if (parser_colour.mastering_metadata) { + mkvmuxer::MasteringMetadata muxer_mm; + if (!CopyMasteringMetadata(*parser_colour.mastering_metadata, &muxer_mm)) + return false; + if (!muxer_colour->SetMasteringMetadata(muxer_mm)) + return false; + } + return true; +} + +// Format of VPx private data: +// +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID Byte | Length | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +// | | +// : Bytes 1..Length of Codec Feature : +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// ID Byte Format +// ID byte is an unsigned byte. +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X| ID | +// +-+-+-+-+-+-+-+-+ +// +// The X bit is reserved. +// +// See the following link for more information: +// http://www.webmproject.org/vp9/profiles/ +bool ParseVpxCodecPrivate(const uint8_t* private_data, int32_t length, + Vp9CodecFeatures* features) { + const int kVpxCodecPrivateMinLength = 3; + if (!private_data || !features || length < kVpxCodecPrivateMinLength) + return false; + + const uint8_t kVp9ProfileId = 1; + const uint8_t kVp9LevelId = 2; + const uint8_t kVp9BitDepthId = 3; + const uint8_t kVp9ChromaSubsamplingId = 4; + const int kVpxFeatureLength = 1; + int offset = 0; + + // Set features to not set. + features->profile = Vp9CodecFeatures::kValueNotPresent; + features->level = Vp9CodecFeatures::kValueNotPresent; + features->bit_depth = Vp9CodecFeatures::kValueNotPresent; + features->chroma_subsampling = Vp9CodecFeatures::kValueNotPresent; + do { + const uint8_t id_byte = private_data[offset++]; + const uint8_t length_byte = private_data[offset++]; + if (length_byte != kVpxFeatureLength) + return false; + if (id_byte == kVp9ProfileId) { + const int priv_profile = static_cast<int>(private_data[offset++]); + if (priv_profile < 0 || priv_profile > 3) + return false; + if (features->profile != Vp9CodecFeatures::kValueNotPresent && + features->profile != priv_profile) { + return false; + } + features->profile = priv_profile; + } else if (id_byte == kVp9LevelId) { + const int priv_level = static_cast<int>(private_data[offset++]); + + const int kNumLevels = 14; + const int levels[kNumLevels] = {10, 11, 20, 21, 30, 31, 40, + 41, 50, 51, 52, 60, 61, 62}; + + for (int i = 0; i < kNumLevels; ++i) { + if (priv_level == levels[i]) { + if (features->level != Vp9CodecFeatures::kValueNotPresent && + features->level != priv_level) { + return false; + } + features->level = priv_level; + break; + } + } + if (features->level == Vp9CodecFeatures::kValueNotPresent) + return false; + } else if (id_byte == kVp9BitDepthId) { + const int priv_profile = static_cast<int>(private_data[offset++]); + if (priv_profile != 8 && priv_profile != 10 && priv_profile != 12) + return false; + if (features->bit_depth != Vp9CodecFeatures::kValueNotPresent && + features->bit_depth != priv_profile) { + return false; + } + features->bit_depth = priv_profile; + } else if (id_byte == kVp9ChromaSubsamplingId) { + const int priv_profile = static_cast<int>(private_data[offset++]); + if (priv_profile != 0 && priv_profile != 2 && priv_profile != 3) + return false; + if (features->chroma_subsampling != Vp9CodecFeatures::kValueNotPresent && + features->chroma_subsampling != priv_profile) { + return false; + } + features->chroma_subsampling = priv_profile; + } else { + // Invalid ID. + return false; + } + } while (offset + kVpxCodecPrivateMinLength <= length); + + return true; +} +} // namespace libwebm diff --git a/common/hdr_util.h b/common/hdr_util.h new file mode 100644 index 0000000..78e2eeb --- /dev/null +++ b/common/hdr_util.h @@ -0,0 +1,71 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_COMMON_HDR_UTIL_H_ +#define LIBWEBM_COMMON_HDR_UTIL_H_ + +#include <stdint.h> + +#include <memory> + +#include "mkvmuxer/mkvmuxer.h" + +namespace mkvparser { +struct Colour; +struct MasteringMetadata; +struct PrimaryChromaticity; +} // namespace mkvparser + +namespace libwebm { +// Utility types and functions for working with the Colour element and its +// children. Copiers return true upon success. Presence functions return true +// when the specified element is present. + +// TODO(tomfinegan): These should be moved to libwebm_utils once c++11 is +// required by libwebm. + +// Features of the VP9 codec that may be set in the CodecPrivate of a VP9 video +// stream. A value of kValueNotPresent represents that the value was not set in +// the CodecPrivate. +struct Vp9CodecFeatures { + static const int kValueNotPresent; + + Vp9CodecFeatures() + : profile(kValueNotPresent), + level(kValueNotPresent), + bit_depth(kValueNotPresent), + chroma_subsampling(kValueNotPresent) {} + ~Vp9CodecFeatures() {} + + int profile; + int level; + int bit_depth; + int chroma_subsampling; +}; + +typedef std::unique_ptr<mkvmuxer::PrimaryChromaticity> PrimaryChromaticityPtr; + +bool CopyPrimaryChromaticity(const mkvparser::PrimaryChromaticity& parser_pc, + PrimaryChromaticityPtr* muxer_pc); + +bool MasteringMetadataValuePresent(double value); + +bool CopyMasteringMetadata(const mkvparser::MasteringMetadata& parser_mm, + mkvmuxer::MasteringMetadata* muxer_mm); + +bool ColourValuePresent(long long value); + +bool CopyColour(const mkvparser::Colour& parser_colour, + mkvmuxer::Colour* muxer_colour); + +// Returns true if |features| is set to one or more valid values. +bool ParseVpxCodecPrivate(const uint8_t* private_data, int32_t length, + Vp9CodecFeatures* features); + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_HDR_UTIL_H_ diff --git a/common/indent.cc b/common/indent.cc new file mode 100644 index 0000000..87d656c --- /dev/null +++ b/common/indent.cc @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2012 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "common/indent.h" + +#include <string> + +namespace libwebm { + +Indent::Indent(int indent) : indent_(indent), indent_str_() { Update(); } + +void Indent::Adjust(int indent) { + indent_ += indent; + if (indent_ < 0) + indent_ = 0; + + Update(); +} + +void Indent::Update() { indent_str_ = std::string(indent_, ' '); } + +} // namespace libwebm diff --git a/common/indent.h b/common/indent.h new file mode 100644 index 0000000..22d3d26 --- /dev/null +++ b/common/indent.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef LIBWEBM_COMMON_INDENT_H_ +#define LIBWEBM_COMMON_INDENT_H_ + +#include <string> + +#include "mkvmuxer/mkvmuxertypes.h" + +namespace libwebm { + +const int kIncreaseIndent = 2; +const int kDecreaseIndent = -2; + +// Used for formatting output so objects only have to keep track of spacing +// within their scope. +class Indent { + public: + explicit Indent(int indent); + + // Changes the number of spaces output. The value adjusted is relative to + // |indent_|. + void Adjust(int indent); + + std::string indent_str() const { return indent_str_; } + + private: + // Called after |indent_| is changed. This will set |indent_str_| to the + // proper number of spaces. + void Update(); + + int indent_; + std::string indent_str_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Indent); +}; + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_INDENT_H_ diff --git a/common/libwebm_util.cc b/common/libwebm_util.cc new file mode 100644 index 0000000..d158250 --- /dev/null +++ b/common/libwebm_util.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2015 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/libwebm_util.h" + +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <limits> + +namespace libwebm { + +std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds) { + const double pts_seconds = nanoseconds / kNanosecondsPerSecond; + return static_cast<std::int64_t>(pts_seconds * 90000); +} + +std::int64_t Khz90TicksToNanoseconds(std::int64_t ticks) { + const double seconds = ticks / 90000.0; + return static_cast<std::int64_t>(seconds * kNanosecondsPerSecond); +} + +bool ParseVP9SuperFrameIndex(const std::uint8_t* frame, + std::size_t frame_length, Ranges* frame_ranges, + bool* error) { + if (frame == nullptr || frame_length == 0 || frame_ranges == nullptr || + error == nullptr) { + return false; + } + + bool parse_ok = false; + const std::uint8_t marker = frame[frame_length - 1]; + const std::uint32_t kHasSuperFrameIndexMask = 0xe0; + const std::uint32_t kSuperFrameMarker = 0xc0; + const std::uint32_t kLengthFieldSizeMask = 0x3; + + if ((marker & kHasSuperFrameIndexMask) == kSuperFrameMarker) { + const std::uint32_t kFrameCountMask = 0x7; + const int num_frames = (marker & kFrameCountMask) + 1; + const int length_field_size = ((marker >> 3) & kLengthFieldSizeMask) + 1; + const std::size_t index_length = 2 + length_field_size * num_frames; + + if (frame_length < index_length) { + std::fprintf(stderr, + "VP9ParseSuperFrameIndex: Invalid superframe index size.\n"); + *error = true; + return false; + } + + // Consume the super frame index. Note: it's at the end of the super frame. + const std::size_t length = frame_length - index_length; + + if (length >= index_length && + frame[frame_length - index_length] == marker) { + // Found a valid superframe index. + const std::uint8_t* byte = frame + length + 1; + + std::size_t frame_offset = 0; + + for (int i = 0; i < num_frames; ++i) { + std::uint32_t child_frame_length = 0; + + for (int j = 0; j < length_field_size; ++j) { + child_frame_length |= (*byte++) << (j * 8); + } + + if (length - frame_offset < child_frame_length) { + std::fprintf(stderr, + "ParseVP9SuperFrameIndex: Invalid superframe, sub frame " + "larger than entire frame.\n"); + *error = true; + return false; + } + + frame_ranges->push_back(Range(frame_offset, child_frame_length)); + frame_offset += child_frame_length; + } + + if (static_cast<int>(frame_ranges->size()) != num_frames) { + std::fprintf(stderr, "VP9Parse: superframe index parse failed.\n"); + *error = true; + return false; + } + + parse_ok = true; + } else { + std::fprintf(stderr, "VP9Parse: Invalid superframe index.\n"); + *error = true; + } + } + return parse_ok; +} + +bool WriteUint8(std::uint8_t val, std::FILE* fileptr) { + if (fileptr == nullptr) + return false; + return (std::fputc(val, fileptr) == val); +} + +std::uint16_t ReadUint16(const std::uint8_t* buf) { + if (buf == nullptr) + return 0; + return ((buf[0] << 8) | buf[1]); +} + +} // namespace libwebm diff --git a/common/libwebm_util.h b/common/libwebm_util.h new file mode 100644 index 0000000..71d805f --- /dev/null +++ b/common/libwebm_util.h @@ -0,0 +1,65 @@ +// Copyright (c) 2015 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_COMMON_LIBWEBM_UTIL_H_ +#define LIBWEBM_COMMON_LIBWEBM_UTIL_H_ + +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <memory> +#include <vector> + +namespace libwebm { + +const double kNanosecondsPerSecond = 1000000000.0; + +// fclose functor for wrapping FILE in std::unique_ptr. +// TODO(tomfinegan): Move this to file_util once c++11 restrictions are +// relaxed. +struct FILEDeleter { + int operator()(std::FILE* f) { + if (f != nullptr) + return fclose(f); + return 0; + } +}; +typedef std::unique_ptr<std::FILE, FILEDeleter> FilePtr; + +struct Range { + Range(std::size_t off, std::size_t len) : offset(off), length(len) {} + Range() = delete; + Range(const Range&) = default; + Range(Range&&) = default; + ~Range() = default; + const std::size_t offset; + const std::size_t length; +}; +typedef std::vector<Range> Ranges; + +// Converts |nanoseconds| to 90000 Hz clock ticks and vice versa. Each return +// the converted value. +std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds); +std::int64_t Khz90TicksToNanoseconds(std::int64_t khz90_ticks); + +// Returns true and stores frame offsets and lengths in |frame_ranges| when +// |frame| has a valid VP9 super frame index. Sets |error| to true when parsing +// fails for any reason. +bool ParseVP9SuperFrameIndex(const std::uint8_t* frame, + std::size_t frame_length, Ranges* frame_ranges, + bool* error); + +// Writes |val| to |fileptr| and returns true upon success. +bool WriteUint8(std::uint8_t val, std::FILE* fileptr); + +// Reads 2 bytes from |buf| and returns them as a uint16_t. Returns 0 when |buf| +// is a nullptr. +std::uint16_t ReadUint16(const std::uint8_t* buf); + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_LIBWEBM_UTIL_H_ diff --git a/common/video_frame.cc b/common/video_frame.cc new file mode 100644 index 0000000..00aa8da --- /dev/null +++ b/common/video_frame.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/video_frame.h" + +#include <cstdio> + +namespace libwebm { + +bool VideoFrame::Buffer::Init(std::size_t new_length) { + capacity = 0; + length = 0; + data.reset(new std::uint8_t[new_length]); + + if (data.get() == nullptr) { + fprintf(stderr, "VideoFrame: Out of memory."); + return false; + } + + capacity = new_length; + length = 0; + return true; +} + +bool VideoFrame::Init(std::size_t length) { return buffer_.Init(length); } + +bool VideoFrame::Init(std::size_t length, std::int64_t nano_pts, Codec codec) { + nanosecond_pts_ = nano_pts; + codec_ = codec; + return Init(length); +} + +bool VideoFrame::SetBufferLength(std::size_t length) { + if (length > buffer_.capacity || buffer_.data.get() == nullptr) + return false; + + buffer_.length = length; + return true; +} + +} // namespace libwebm diff --git a/common/video_frame.h b/common/video_frame.h new file mode 100644 index 0000000..3031ef2 --- /dev/null +++ b/common/video_frame.h @@ -0,0 +1,68 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_COMMON_VIDEO_FRAME_H_ +#define LIBWEBM_COMMON_VIDEO_FRAME_H_ + +#include <cstdint> +#include <memory> + +namespace libwebm { + +// VideoFrame is a storage class for compressed video frames. +class VideoFrame { + public: + enum Codec { kVP8, kVP9 }; + struct Buffer { + Buffer() = default; + ~Buffer() = default; + + // Resets |data| to be of size |new_length| bytes, sets |capacity| to + // |new_length|, sets |length| to 0 (aka empty). Returns true for success. + bool Init(std::size_t new_length); + + std::unique_ptr<std::uint8_t[]> data; + std::size_t length = 0; + std::size_t capacity = 0; + }; + + VideoFrame() = default; + ~VideoFrame() = default; + VideoFrame(std::int64_t pts_in_nanoseconds, Codec vpx_codec) + : nanosecond_pts_(pts_in_nanoseconds), codec_(vpx_codec) {} + VideoFrame(bool keyframe, std::int64_t pts_in_nanoseconds, Codec vpx_codec) + : keyframe_(keyframe), + nanosecond_pts_(pts_in_nanoseconds), + codec_(vpx_codec) {} + bool Init(std::size_t length); + bool Init(std::size_t length, std::int64_t nano_pts, Codec codec); + + // Updates actual length of data stored in |buffer_.data| when it's been + // written via the raw pointer returned from buffer_.data.get(). + // Returns false when buffer_.data.get() return nullptr and/or when + // |length| > |buffer_.length|. Returns true otherwise. + bool SetBufferLength(std::size_t length); + + // Accessors. + const Buffer& buffer() const { return buffer_; } + bool keyframe() const { return keyframe_; } + std::int64_t nanosecond_pts() const { return nanosecond_pts_; } + Codec codec() const { return codec_; } + + // Mutators. + void set_nanosecond_pts(std::int64_t nano_pts) { nanosecond_pts_ = nano_pts; } + + private: + Buffer buffer_; + bool keyframe_ = false; + std::int64_t nanosecond_pts_ = 0; + Codec codec_ = kVP9; +}; + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_VIDEO_FRAME_H_
\ No newline at end of file diff --git a/common/vp9_header_parser.cc b/common/vp9_header_parser.cc new file mode 100644 index 0000000..604a050 --- /dev/null +++ b/common/vp9_header_parser.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/vp9_header_parser.h" + +#include <stdio.h> + +namespace vp9_parser { + +bool Vp9HeaderParser::SetFrame(const uint8_t* frame, size_t length) { + if (!frame || length == 0) + return false; + + frame_ = frame; + frame_size_ = length; + bit_offset_ = 0; + profile_ = -1; + show_existing_frame_ = 0; + key_ = 0; + altref_ = 0; + error_resilient_mode_ = 0; + intra_only_ = 0; + reset_frame_context_ = 0; + color_space_ = 0; + color_range_ = 0; + subsampling_x_ = 0; + subsampling_y_ = 0; + refresh_frame_flags_ = 0; + return true; +} + +bool Vp9HeaderParser::ParseUncompressedHeader(const uint8_t* frame, + size_t length) { + if (!SetFrame(frame, length)) + return false; + const int frame_marker = VpxReadLiteral(2); + if (frame_marker != kVp9FrameMarker) { + fprintf(stderr, "Invalid VP9 frame_marker:%d\n", frame_marker); + return false; + } + + profile_ = ReadBit(); + profile_ |= ReadBit() << 1; + if (profile_ > 2) + profile_ += ReadBit(); + + // TODO(fgalligan): Decide how to handle show existing frames. + show_existing_frame_ = ReadBit(); + if (show_existing_frame_) + return true; + + key_ = !ReadBit(); + altref_ = !ReadBit(); + error_resilient_mode_ = ReadBit(); + if (key_) { + if (!ValidateVp9SyncCode()) { + fprintf(stderr, "Invalid Sync code!\n"); + return false; + } + + ParseColorSpace(); + ParseFrameResolution(); + ParseFrameParallelMode(); + ParseTileInfo(); + } else { + intra_only_ = altref_ ? ReadBit() : 0; + reset_frame_context_ = error_resilient_mode_ ? 0 : VpxReadLiteral(2); + if (intra_only_) { + if (!ValidateVp9SyncCode()) { + fprintf(stderr, "Invalid Sync code!\n"); + return false; + } + + if (profile_ > 0) { + ParseColorSpace(); + } else { + // NOTE: The intra-only frame header does not include the specification + // of either the color format or color sub-sampling in profile 0. VP9 + // specifies that the default color format should be YUV 4:2:0 in this + // case (normative). + color_space_ = kVpxCsBt601; + color_range_ = kVpxCrStudioRange; + subsampling_y_ = subsampling_x_ = 1; + bit_depth_ = 8; + } + + refresh_frame_flags_ = VpxReadLiteral(kRefFrames); + ParseFrameResolution(); + } else { + refresh_frame_flags_ = VpxReadLiteral(kRefFrames); + for (int i = 0; i < kRefsPerFrame; ++i) { + VpxReadLiteral(kRefFrames_LOG2); // Consume ref. + ReadBit(); // Consume ref sign bias. + } + + bool found = false; + for (int i = 0; i < kRefsPerFrame; ++i) { + if (ReadBit()) { + // Found previous reference, width and height did not change since + // last frame. + found = true; + break; + } + } + + if (!found) + ParseFrameResolution(); + } + } + return true; +} + +int Vp9HeaderParser::ReadBit() { + const size_t off = bit_offset_; + const size_t byte_offset = off >> 3; + const int bit_shift = 7 - static_cast<int>(off & 0x7); + if (byte_offset < frame_size_) { + const int bit = (frame_[byte_offset] >> bit_shift) & 1; + bit_offset_++; + return bit; + } else { + return 0; + } +} + +int Vp9HeaderParser::VpxReadLiteral(int bits) { + int value = 0; + for (int bit = bits - 1; bit >= 0; --bit) + value |= ReadBit() << bit; + return value; +} + +bool Vp9HeaderParser::ValidateVp9SyncCode() { + const int sync_code_0 = VpxReadLiteral(8); + const int sync_code_1 = VpxReadLiteral(8); + const int sync_code_2 = VpxReadLiteral(8); + return (sync_code_0 == 0x49 && sync_code_1 == 0x83 && sync_code_2 == 0x42); +} + +void Vp9HeaderParser::ParseColorSpace() { + bit_depth_ = 0; + if (profile_ >= 2) + bit_depth_ = ReadBit() ? 12 : 10; + else + bit_depth_ = 8; + color_space_ = VpxReadLiteral(3); + if (color_space_ != kVpxCsSrgb) { + color_range_ = ReadBit(); + if (profile_ == 1 || profile_ == 3) { + subsampling_x_ = ReadBit(); + subsampling_y_ = ReadBit(); + ReadBit(); + } else { + subsampling_y_ = subsampling_x_ = 1; + } + } else { + color_range_ = kVpxCrFullRange; + if (profile_ == 1 || profile_ == 3) { + subsampling_y_ = subsampling_x_ = 0; + ReadBit(); + } + } +} + +void Vp9HeaderParser::ParseFrameResolution() { + width_ = VpxReadLiteral(16) + 1; + height_ = VpxReadLiteral(16) + 1; +} + +void Vp9HeaderParser::ParseFrameParallelMode() { + if (ReadBit()) { + VpxReadLiteral(16); // display width + VpxReadLiteral(16); // display height + } + if (!error_resilient_mode_) { + ReadBit(); // Consume refresh frame context + frame_parallel_mode_ = ReadBit(); + } else { + frame_parallel_mode_ = 1; + } +} + +void Vp9HeaderParser::ParseTileInfo() { + VpxReadLiteral(2); // Consume frame context index + + // loopfilter + VpxReadLiteral(6); // Consume filter level + VpxReadLiteral(3); // Consume sharpness level + + const bool mode_ref_delta_enabled = ReadBit(); + if (mode_ref_delta_enabled) { + const bool mode_ref_delta_update = ReadBit(); + if (mode_ref_delta_update) { + const int kMaxRefLFDeltas = 4; + for (int i = 0; i < kMaxRefLFDeltas; ++i) { + if (ReadBit()) + VpxReadLiteral(7); // Consume ref_deltas + sign + } + + const int kMaxModeDeltas = 2; + for (int i = 0; i < kMaxModeDeltas; ++i) { + if (ReadBit()) + VpxReadLiteral(7); // Consume mode_delta + sign + } + } + } + + // quantization + VpxReadLiteral(8); // Consume base_q + SkipDeltaQ(); // y dc + SkipDeltaQ(); // uv ac + SkipDeltaQ(); // uv dc + + // segmentation + const bool segmentation_enabled = ReadBit(); + if (!segmentation_enabled) { + const int aligned_width = AlignPowerOfTwo(width_, kMiSizeLog2); + const int mi_cols = aligned_width >> kMiSizeLog2; + const int aligned_mi_cols = AlignPowerOfTwo(mi_cols, kMiSizeLog2); + const int sb_cols = aligned_mi_cols >> 3; // to_sbs(mi_cols); + int min_log2_n_tiles, max_log2_n_tiles; + + for (max_log2_n_tiles = 0; + (sb_cols >> max_log2_n_tiles) >= kMinTileWidthB64; + max_log2_n_tiles++) { + } + max_log2_n_tiles--; + if (max_log2_n_tiles < 0) + max_log2_n_tiles = 0; + + for (min_log2_n_tiles = 0; (kMaxTileWidthB64 << min_log2_n_tiles) < sb_cols; + min_log2_n_tiles++) { + } + + // columns + const int max_log2_tile_cols = max_log2_n_tiles; + const int min_log2_tile_cols = min_log2_n_tiles; + int max_ones = max_log2_tile_cols - min_log2_tile_cols; + int log2_tile_cols = min_log2_tile_cols; + while (max_ones-- && ReadBit()) + log2_tile_cols++; + + // rows + int log2_tile_rows = ReadBit(); + if (log2_tile_rows) + log2_tile_rows += ReadBit(); + + row_tiles_ = 1 << log2_tile_rows; + column_tiles_ = 1 << log2_tile_cols; + } +} + +void Vp9HeaderParser::SkipDeltaQ() { + if (ReadBit()) + VpxReadLiteral(4); +} + +int Vp9HeaderParser::AlignPowerOfTwo(int value, int n) { + return (((value) + ((1 << (n)) - 1)) & ~((1 << (n)) - 1)); +} + +} // namespace vp9_parser diff --git a/common/vp9_header_parser.h b/common/vp9_header_parser.h new file mode 100644 index 0000000..06bd656 --- /dev/null +++ b/common/vp9_header_parser.h @@ -0,0 +1,125 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_COMMON_VP9_HEADER_PARSER_H_ +#define LIBWEBM_COMMON_VP9_HEADER_PARSER_H_ + +#include <stddef.h> +#include <stdint.h> + +namespace vp9_parser { + +const int kVp9FrameMarker = 2; +const int kMinTileWidthB64 = 4; +const int kMaxTileWidthB64 = 64; +const int kRefFrames = 8; +const int kRefsPerFrame = 3; +const int kRefFrames_LOG2 = 3; +const int kVpxCsBt601 = 1; +const int kVpxCsSrgb = 7; +const int kVpxCrStudioRange = 0; +const int kVpxCrFullRange = 1; +const int kMiSizeLog2 = 3; + +// Class to parse the header of a VP9 frame. +class Vp9HeaderParser { + public: + Vp9HeaderParser() + : frame_(NULL), + frame_size_(0), + bit_offset_(0), + profile_(-1), + show_existing_frame_(0), + key_(0), + altref_(0), + error_resilient_mode_(0), + intra_only_(0), + reset_frame_context_(0), + bit_depth_(0), + color_space_(0), + color_range_(0), + subsampling_x_(0), + subsampling_y_(0), + refresh_frame_flags_(0), + width_(0), + height_(0), + row_tiles_(0), + column_tiles_(0), + frame_parallel_mode_(0) {} + + // Parse the VP9 uncompressed header. The return values of the remaining + // functions are only valid on success. + bool ParseUncompressedHeader(const uint8_t* frame, size_t length); + + size_t frame_size() const { return frame_size_; } + int profile() const { return profile_; } + int key() const { return key_; } + int altref() const { return altref_; } + int error_resilient_mode() const { return error_resilient_mode_; } + int bit_depth() const { return bit_depth_; } + int color_space() const { return color_space_; } + int width() const { return width_; } + int height() const { return height_; } + int refresh_frame_flags() const { return refresh_frame_flags_; } + int row_tiles() const { return row_tiles_; } + int column_tiles() const { return column_tiles_; } + int frame_parallel_mode() const { return frame_parallel_mode_; } + + private: + // Set the compressed VP9 frame. + bool SetFrame(const uint8_t* frame, size_t length); + + // Returns the next bit of the frame. + int ReadBit(); + + // Returns the next |bits| of the frame. + int VpxReadLiteral(int bits); + + // Returns true if the vp9 sync code is valid. + bool ValidateVp9SyncCode(); + + // Parses bit_depth_, color_space_, subsampling_x_, subsampling_y_, and + // color_range_. + void ParseColorSpace(); + + // Parses width and height of the frame. + void ParseFrameResolution(); + + // Parses frame_parallel_mode_. This function skips over some features. + void ParseFrameParallelMode(); + + // Parses row and column tiles. This function skips over some features. + void ParseTileInfo(); + void SkipDeltaQ(); + int AlignPowerOfTwo(int value, int n); + + const uint8_t* frame_; + size_t frame_size_; + size_t bit_offset_; + int profile_; + int show_existing_frame_; + int key_; + int altref_; + int error_resilient_mode_; + int intra_only_; + int reset_frame_context_; + int bit_depth_; + int color_space_; + int color_range_; + int subsampling_x_; + int subsampling_y_; + int refresh_frame_flags_; + int width_; + int height_; + int row_tiles_; + int column_tiles_; + int frame_parallel_mode_; +}; + +} // namespace vp9_parser + +#endif // LIBWEBM_COMMON_VP9_HEADER_PARSER_H_ diff --git a/common/vp9_header_parser_tests.cc b/common/vp9_header_parser_tests.cc new file mode 100644 index 0000000..d13bff2 --- /dev/null +++ b/common/vp9_header_parser_tests.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/vp9_header_parser.h" + +#include <string> + +#include "gtest/gtest.h" + +#include "common/hdr_util.h" +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" +#include "testing/test_util.h" + +namespace { + +class Vp9HeaderParserTests : public ::testing::Test { + public: + Vp9HeaderParserTests() : is_reader_open_(false), segment_(NULL) {} + + ~Vp9HeaderParserTests() override { + CloseReader(); + if (segment_ != NULL) { + delete segment_; + segment_ = NULL; + } + } + + void CloseReader() { + if (is_reader_open_) { + reader_.Close(); + } + is_reader_open_ = false; + } + + void CreateAndLoadSegment(const std::string& filename, + int expected_doc_type_ver) { + filename_ = test::GetTestFilePath(filename); + ASSERT_EQ(0, reader_.Open(filename_.c_str())); + is_reader_open_ = true; + pos_ = 0; + mkvparser::EBMLHeader ebml_header; + ebml_header.Parse(&reader_, pos_); + ASSERT_EQ(1, ebml_header.m_version); + ASSERT_EQ(1, ebml_header.m_readVersion); + ASSERT_STREQ("webm", ebml_header.m_docType); + ASSERT_EQ(expected_doc_type_ver, ebml_header.m_docTypeVersion); + ASSERT_EQ(2, ebml_header.m_docTypeReadVersion); + ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, segment_)); + ASSERT_FALSE(HasFailure()); + ASSERT_GE(0, segment_->Load()); + } + + void CreateAndLoadSegment(const std::string& filename) { + CreateAndLoadSegment(filename, 4); + } + + // Load a corrupted segment with no expectation of correctness. + void CreateAndLoadInvalidSegment(const std::string& filename) { + filename_ = test::GetTestFilePath(filename); + ASSERT_EQ(0, reader_.Open(filename_.c_str())); + is_reader_open_ = true; + pos_ = 0; + mkvparser::EBMLHeader ebml_header; + ebml_header.Parse(&reader_, pos_); + ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, segment_)); + ASSERT_GE(0, segment_->Load()); + } + + void ProcessTheFrames(bool invalid_bitstream) { + unsigned char* data = NULL; + size_t data_len = 0; + const mkvparser::Tracks* const parser_tracks = segment_->GetTracks(); + ASSERT_TRUE(parser_tracks != NULL); + const mkvparser::Cluster* cluster = segment_->GetFirst(); + ASSERT_TRUE(cluster != NULL); + + while ((cluster != NULL) && !cluster->EOS()) { + const mkvparser::BlockEntry* block_entry; + long status = cluster->GetFirst(block_entry); // NOLINT + ASSERT_EQ(0, status); + + while ((block_entry != NULL) && !block_entry->EOS()) { + const mkvparser::Block* const block = block_entry->GetBlock(); + ASSERT_TRUE(block != NULL); + const long long trackNum = block->GetTrackNumber(); // NOLINT + const mkvparser::Track* const parser_track = + parser_tracks->GetTrackByNumber( + static_cast<unsigned long>(trackNum)); // NOLINT + ASSERT_TRUE(parser_track != NULL); + const long long track_type = parser_track->GetType(); // NOLINT + + if (track_type == mkvparser::Track::kVideo) { + const int frame_count = block->GetFrameCount(); + + for (int i = 0; i < frame_count; ++i) { + const mkvparser::Block::Frame& frame = block->GetFrame(i); + + if (static_cast<size_t>(frame.len) > data_len) { + delete[] data; + data = new unsigned char[frame.len]; + ASSERT_TRUE(data != NULL); + data_len = static_cast<size_t>(frame.len); + } + ASSERT_FALSE(frame.Read(&reader_, data)); + ASSERT_EQ(parser_.ParseUncompressedHeader(data, data_len), + !invalid_bitstream); + } + } + + status = cluster->GetNext(block_entry, block_entry); + ASSERT_EQ(0, status); + } + + cluster = segment_->GetNext(cluster); + } + delete[] data; + } + + protected: + mkvparser::MkvReader reader_; + bool is_reader_open_; + mkvparser::Segment* segment_; + std::string filename_; + long long pos_; // NOLINT + vp9_parser::Vp9HeaderParser parser_; +}; + +TEST_F(Vp9HeaderParserTests, VideoOnlyFile) { + ASSERT_NO_FATAL_FAILURE(CreateAndLoadSegment("test_stereo_left_right.webm")); + ProcessTheFrames(false); + EXPECT_EQ(256, parser_.width()); + EXPECT_EQ(144, parser_.height()); + EXPECT_EQ(1, parser_.column_tiles()); + EXPECT_EQ(0, parser_.frame_parallel_mode()); +} + +TEST_F(Vp9HeaderParserTests, Muxed) { + ASSERT_NO_FATAL_FAILURE( + CreateAndLoadSegment("bbb_480p_vp9_opus_1second.webm", 4)); + ProcessTheFrames(false); + EXPECT_EQ(854, parser_.width()); + EXPECT_EQ(480, parser_.height()); + EXPECT_EQ(2, parser_.column_tiles()); + EXPECT_EQ(1, parser_.frame_parallel_mode()); +} + +TEST_F(Vp9HeaderParserTests, Invalid) { + const char* files[] = { + "invalid/invalid_vp9_bitstream-bug_1416.webm", + "invalid/invalid_vp9_bitstream-bug_1417.webm", + }; + + for (int i = 0; i < static_cast<int>(sizeof(files) / sizeof(files[0])); ++i) { + SCOPED_TRACE(files[i]); + ASSERT_NO_FATAL_FAILURE(CreateAndLoadInvalidSegment(files[i])); + ProcessTheFrames(true); + CloseReader(); + delete segment_; + segment_ = NULL; + } +} + +TEST_F(Vp9HeaderParserTests, API) { + vp9_parser::Vp9HeaderParser parser; + uint8_t data; + EXPECT_FALSE(parser.ParseUncompressedHeader(NULL, 0)); + EXPECT_FALSE(parser.ParseUncompressedHeader(NULL, 10)); + EXPECT_FALSE(parser.ParseUncompressedHeader(&data, 0)); +} + +} // namespace + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/common/vp9_level_stats.cc b/common/vp9_level_stats.cc new file mode 100644 index 0000000..76891e6 --- /dev/null +++ b/common/vp9_level_stats.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/vp9_level_stats.h" + +#include <inttypes.h> + +#include <limits> +#include <utility> + +#include "common/webm_constants.h" + +namespace vp9_parser { + +const Vp9LevelRow Vp9LevelStats::Vp9LevelTable[kNumVp9Levels] = { + {LEVEL_1, 829440, 36864, 200, 400, 2, 1, 4, 8, 512}, + {LEVEL_1_1, 2764800, 73728, 800, 1000, 2, 1, 4, 8, 768}, + {LEVEL_2, 4608000, 122880, 1800, 1500, 2, 1, 4, 8, 960}, + {LEVEL_2_1, 9216000, 245760, 3600, 2800, 2, 2, 4, 8, 1344}, + {LEVEL_3, 20736000, 552960, 7200, 6000, 2, 4, 4, 8, 2048}, + {LEVEL_3_1, 36864000, 983040, 12000, 10000, 2, 4, 4, 8, 2752}, + {LEVEL_4, 83558400, 2228224, 18000, 16000, 4, 4, 4, 8, 4160}, + {LEVEL_4_1, 160432128, 2228224, 30000, 18000, 4, 4, 5, 6, 4160}, + {LEVEL_5, 311951360, 8912896, 60000, 36000, 6, 8, 6, 4, 8384}, + {LEVEL_5_1, 588251136, 8912896, 120000, 46000, 8, 8, 10, 4, 8384}, + // CPB Size = 0 for levels 5_2 to 6_2 + {LEVEL_5_2, 1176502272, 8912896, 180000, 0, 8, 8, 10, 4, 8384}, + {LEVEL_6, 1176502272, 35651584, 180000, 0, 8, 16, 10, 4, 16832}, + {LEVEL_6_1, 2353004544, 35651584, 240000, 0, 8, 16, 10, 4, 16832}, + {LEVEL_6_2, 4706009088, 35651584, 480000, 0, 8, 16, 10, 4, 16832}}; + +void Vp9LevelStats::AddFrame(const Vp9HeaderParser& parser, int64_t time_ns) { + ++frames; + if (start_ns_ == -1) + start_ns_ = time_ns; + end_ns_ = time_ns; + + const int width = parser.width(); + const int height = parser.height(); + const int64_t luma_picture_size = width * height; + const int64_t luma_picture_breadth = (width > height) ? width : height; + if (luma_picture_size > max_luma_picture_size_) + max_luma_picture_size_ = luma_picture_size; + if (luma_picture_breadth > max_luma_picture_breadth_) + max_luma_picture_breadth_ = luma_picture_breadth; + + total_compressed_size_ += parser.frame_size(); + + while (!luma_window_.empty() && + luma_window_.front().first < + (time_ns - (libwebm::kNanosecondsPerSecondi - 1))) { + current_luma_size_ -= luma_window_.front().second; + luma_window_.pop(); + } + current_luma_size_ += luma_picture_size; + luma_window_.push(std::make_pair(time_ns, luma_picture_size)); + if (current_luma_size_ > max_luma_size_) { + max_luma_size_ = current_luma_size_; + max_luma_end_ns_ = luma_window_.back().first; + } + + // Record CPB stats. + // Remove all frames that are less than window size. + while (cpb_window_.size() > 3) { + current_cpb_size_ -= cpb_window_.front().second; + cpb_window_.pop(); + } + cpb_window_.push(std::make_pair(time_ns, parser.frame_size())); + + current_cpb_size_ += parser.frame_size(); + if (current_cpb_size_ > max_cpb_size_) { + max_cpb_size_ = current_cpb_size_; + max_cpb_start_ns_ = cpb_window_.front().first; + max_cpb_end_ns_ = cpb_window_.back().first; + } + + if (max_cpb_window_size_ < static_cast<int64_t>(cpb_window_.size())) { + max_cpb_window_size_ = cpb_window_.size(); + max_cpb_window_end_ns_ = time_ns; + } + + // Record altref stats. + if (parser.altref()) { + const int delta_altref = frames_since_last_altref; + if (first_altref) { + first_altref = false; + } else if (delta_altref < minimum_altref_distance) { + minimum_altref_distance = delta_altref; + min_altref_end_ns = time_ns; + } + frames_since_last_altref = 0; + } else { + ++frames_since_last_altref; + ++displayed_frames; + // TODO(fgalligan): Add support for other color formats. Currently assuming + // 420. + total_uncompressed_bits_ += + (luma_picture_size * parser.bit_depth() * 3) / 2; + } + + // Count max reference frames. + if (parser.key() == 1) { + frames_refreshed_ = 0; + } else { + frames_refreshed_ |= parser.refresh_frame_flags(); + + int ref_frame_count = frames_refreshed_ & 1; + for (int i = 1; i < kMaxVp9RefFrames; ++i) { + ref_frame_count += (frames_refreshed_ >> i) & 1; + } + + if (ref_frame_count > max_frames_refreshed_) + max_frames_refreshed_ = ref_frame_count; + } + + // Count max tiles. + const int tiles = parser.column_tiles(); + if (tiles > max_column_tiles_) + max_column_tiles_ = tiles; +} + +Vp9Level Vp9LevelStats::GetLevel() const { + const int64_t max_luma_sample_rate = GetMaxLumaSampleRate(); + const int64_t max_luma_picture_size = GetMaxLumaPictureSize(); + const int64_t max_luma_picture_breadth = GetMaxLumaPictureBreadth(); + const double average_bitrate = GetAverageBitRate(); + const double max_cpb_size = GetMaxCpbSize(); + const double compresion_ratio = GetCompressionRatio(); + const int max_column_tiles = GetMaxColumnTiles(); + const int min_altref_distance = GetMinimumAltrefDistance(); + const int max_ref_frames = GetMaxReferenceFrames(); + + int level_index = 0; + Vp9Level max_level = LEVEL_UNKNOWN; + const double grace_multiplier = + max_luma_sample_rate_grace_percent_ / 100.0 + 1.0; + for (int i = 0; i < kNumVp9Levels; ++i) { + if (max_luma_sample_rate <= + Vp9LevelTable[i].max_luma_sample_rate * grace_multiplier) { + if (max_level < Vp9LevelTable[i].level) { + max_level = Vp9LevelTable[i].level; + level_index = i; + } + break; + } + } + for (int i = 0; i < kNumVp9Levels; ++i) { + if (max_luma_picture_size <= Vp9LevelTable[i].max_luma_picture_size) { + if (max_level < Vp9LevelTable[i].level) { + max_level = Vp9LevelTable[i].level; + level_index = i; + } + break; + } + } + for (int i = 0; i < kNumVp9Levels; ++i) { + if (max_luma_picture_breadth <= Vp9LevelTable[i].max_luma_picture_breadth) { + if (max_level < Vp9LevelTable[i].level) { + max_level = Vp9LevelTable[i].level; + level_index = i; + } + break; + } + } + for (int i = 0; i < kNumVp9Levels; ++i) { + if (average_bitrate <= Vp9LevelTable[i].average_bitrate) { + if (max_level < Vp9LevelTable[i].level) { + max_level = Vp9LevelTable[i].level; + level_index = i; + } + break; + } + } + for (int i = 0; i < kNumVp9Levels; ++i) { + // Only check CPB size for levels that are defined. + if (Vp9LevelTable[i].max_cpb_size > 0 && + max_cpb_size <= Vp9LevelTable[i].max_cpb_size) { + if (max_level < Vp9LevelTable[i].level) { + max_level = Vp9LevelTable[i].level; + level_index = i; + } + break; + } + } + for (int i = 0; i < kNumVp9Levels; ++i) { + if (max_column_tiles <= Vp9LevelTable[i].max_tiles) { + if (max_level < Vp9LevelTable[i].level) { + max_level = Vp9LevelTable[i].level; + level_index = i; + } + break; + } + } + + for (int i = 0; i < kNumVp9Levels; ++i) { + if (max_ref_frames <= Vp9LevelTable[i].max_ref_frames) { + if (max_level < Vp9LevelTable[i].level) { + max_level = Vp9LevelTable[i].level; + level_index = i; + } + break; + } + } + + // Check if the current level meets the minimum altref distance requirement. + // If not, set to unknown level as we can't move up a level as the minimum + // altref distance get farther apart and we can't move down a level as we are + // already at the minimum level for all the other requirements. + if (min_altref_distance < Vp9LevelTable[level_index].min_altref_distance) + max_level = LEVEL_UNKNOWN; + + // The minimum compression ratio has the same behavior as minimum altref + // distance. + if (compresion_ratio < Vp9LevelTable[level_index].compresion_ratio) + max_level = LEVEL_UNKNOWN; + return max_level; +} + +int64_t Vp9LevelStats::GetMaxLumaSampleRate() const { return max_luma_size_; } + +int64_t Vp9LevelStats::GetMaxLumaPictureSize() const { + return max_luma_picture_size_; +} + +int64_t Vp9LevelStats::GetMaxLumaPictureBreadth() const { + return max_luma_picture_breadth_; +} + +double Vp9LevelStats::GetAverageBitRate() const { + const int64_t frame_duration_ns = end_ns_ - start_ns_; + double duration_seconds = + ((duration_ns_ == -1) ? frame_duration_ns : duration_ns_) / + libwebm::kNanosecondsPerSecond; + if (estimate_last_frame_duration_ && + (duration_ns_ == -1 || duration_ns_ <= frame_duration_ns)) { + const double sec_per_frame = frame_duration_ns / + libwebm::kNanosecondsPerSecond / + (displayed_frames - 1); + duration_seconds += sec_per_frame; + } + + return total_compressed_size_ / duration_seconds / 125.0; +} + +double Vp9LevelStats::GetMaxCpbSize() const { return max_cpb_size_ / 125.0; } + +double Vp9LevelStats::GetCompressionRatio() const { + return total_uncompressed_bits_ / + static_cast<double>(total_compressed_size_ * 8); +} + +int Vp9LevelStats::GetMaxColumnTiles() const { return max_column_tiles_; } + +int Vp9LevelStats::GetMinimumAltrefDistance() const { + if (minimum_altref_distance != std::numeric_limits<int>::max()) + return minimum_altref_distance; + else + return -1; +} + +int Vp9LevelStats::GetMaxReferenceFrames() const { + return max_frames_refreshed_; +} + +} // namespace vp9_parser diff --git a/common/vp9_level_stats.h b/common/vp9_level_stats.h new file mode 100644 index 0000000..45d6f5c --- /dev/null +++ b/common/vp9_level_stats.h @@ -0,0 +1,215 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_COMMON_VP9_LEVEL_STATS_H_ +#define LIBWEBM_COMMON_VP9_LEVEL_STATS_H_ + +#include <limits> +#include <queue> +#include <utility> + +#include "common/vp9_header_parser.h" + +namespace vp9_parser { + +const int kMaxVp9RefFrames = 8; + +// Defined VP9 levels. See http://www.webmproject.org/vp9/profiles/ for +// detailed information on VP9 levels. +const int kNumVp9Levels = 14; +enum Vp9Level { + LEVEL_UNKNOWN = 0, + LEVEL_1 = 10, + LEVEL_1_1 = 11, + LEVEL_2 = 20, + LEVEL_2_1 = 21, + LEVEL_3 = 30, + LEVEL_3_1 = 31, + LEVEL_4 = 40, + LEVEL_4_1 = 41, + LEVEL_5 = 50, + LEVEL_5_1 = 51, + LEVEL_5_2 = 52, + LEVEL_6 = 60, + LEVEL_6_1 = 61, + LEVEL_6_2 = 62 +}; + +struct Vp9LevelRow { + Vp9LevelRow() = default; + ~Vp9LevelRow() = default; + Vp9LevelRow(Vp9LevelRow&& other) = default; + Vp9LevelRow(const Vp9LevelRow& other) = default; + Vp9LevelRow& operator=(Vp9LevelRow&& other) = delete; + Vp9LevelRow& operator=(const Vp9LevelRow& other) = delete; + + Vp9Level level; + int64_t max_luma_sample_rate; + int64_t max_luma_picture_size; + int64_t max_luma_picture_breadth; + double average_bitrate; + double max_cpb_size; + double compresion_ratio; + int max_tiles; + int min_altref_distance; + int max_ref_frames; +}; + +// Class to determine the VP9 level of a VP9 bitstream. +class Vp9LevelStats { + public: + static const Vp9LevelRow Vp9LevelTable[kNumVp9Levels]; + + Vp9LevelStats() + : frames(0), + displayed_frames(0), + start_ns_(-1), + end_ns_(-1), + duration_ns_(-1), + max_luma_picture_size_(0), + max_luma_picture_breadth_(0), + current_luma_size_(0), + max_luma_size_(0), + max_luma_end_ns_(0), + max_luma_sample_rate_grace_percent_(1.5), + first_altref(true), + frames_since_last_altref(0), + minimum_altref_distance(std::numeric_limits<int>::max()), + min_altref_end_ns(0), + max_cpb_window_size_(0), + max_cpb_window_end_ns_(0), + current_cpb_size_(0), + max_cpb_size_(0), + max_cpb_start_ns_(0), + max_cpb_end_ns_(0), + total_compressed_size_(0), + total_uncompressed_bits_(0), + frames_refreshed_(0), + max_frames_refreshed_(0), + max_column_tiles_(0), + estimate_last_frame_duration_(true) {} + + ~Vp9LevelStats() = default; + Vp9LevelStats(Vp9LevelStats&& other) = delete; + Vp9LevelStats(const Vp9LevelStats& other) = delete; + Vp9LevelStats& operator=(Vp9LevelStats&& other) = delete; + Vp9LevelStats& operator=(const Vp9LevelStats& other) = delete; + + // Collects stats on a VP9 frame. The frame must already be parsed by + // |parser|. |time_ns| is the start time of the frame in nanoseconds. + void AddFrame(const Vp9HeaderParser& parser, int64_t time_ns); + + // Returns the current VP9 level. All of the video frames should have been + // processed with AddFrame before calling this function. + Vp9Level GetLevel() const; + + // Returns the maximum luma samples (pixels) per second. The Alt-Ref frames + // are taken into account, therefore this number may be larger than the + // display luma samples per second + int64_t GetMaxLumaSampleRate() const; + + // The maximum frame size (width * height) in samples. + int64_t GetMaxLumaPictureSize() const; + + // The maximum frame breadth (max of width and height) in samples. + int64_t GetMaxLumaPictureBreadth() const; + + // The average bitrate of the video in kbps. + double GetAverageBitRate() const; + + // The largest data size for any 4 consecutive frames in kilobits. + double GetMaxCpbSize() const; + + // The ratio of total bytes decompressed over total bytes compressed. + double GetCompressionRatio() const; + + // The maximum number of VP9 column tiles. + int GetMaxColumnTiles() const; + + // The minimum distance in frames between two consecutive alternate reference + // frames. + int GetMinimumAltrefDistance() const; + + // The maximum number of reference frames that had to be stored. + int GetMaxReferenceFrames() const; + + // Sets the duration of the video stream in nanoseconds. If the duration is + // not explictly set by this function then this class will use end - start + // as the duration. + void set_duration(int64_t time_ns) { duration_ns_ = time_ns; } + double max_luma_sample_rate_grace_percent() const { + return max_luma_sample_rate_grace_percent_; + } + void set_max_luma_sample_rate_grace_percent(double percent) { + max_luma_sample_rate_grace_percent_ = percent; + } + bool estimate_last_frame_duration() const { + return estimate_last_frame_duration_; + } + + // If true try to estimate the last frame's duration if the stream's duration + // is not set or the stream's duration equals the last frame's timestamp. + void set_estimate_last_frame_duration(bool flag) { + estimate_last_frame_duration_ = flag; + } + + private: + int frames; + int displayed_frames; + + int64_t start_ns_; + int64_t end_ns_; + int64_t duration_ns_; + + int64_t max_luma_picture_size_; + int64_t max_luma_picture_breadth_; + + // This is used to calculate the maximum number of luma samples per second. + // The first value is the luma picture size and the second value is the time + // in nanoseconds of one frame. + std::queue<std::pair<int64_t, int64_t>> luma_window_; + int64_t current_luma_size_; + int64_t max_luma_size_; + int64_t max_luma_end_ns_; + + // MaxLumaSampleRate = (ExampleFrameRate + ExampleFrameRate / + // MinimumAltrefDistance) * MaxLumaPictureSize. For levels 1-4 + // ExampleFrameRate / MinimumAltrefDistance is non-integer, so using a sliding + // window of one frame to calculate MaxLumaSampleRate may have frames > + // (ExampleFrameRate + ExampleFrameRate / MinimumAltrefDistance) in the + // window. In order to address this issue, a grace percent of 1.5 was added. + double max_luma_sample_rate_grace_percent_; + + bool first_altref; + int frames_since_last_altref; + int minimum_altref_distance; + int64_t min_altref_end_ns; + + // This is used to calculate the maximum number of compressed bytes for four + // consecutive frames. The first value is the compressed frame size and the + // second value is the time in nanoseconds of one frame. + std::queue<std::pair<int64_t, int64_t>> cpb_window_; + int64_t max_cpb_window_size_; + int64_t max_cpb_window_end_ns_; + int64_t current_cpb_size_; + int64_t max_cpb_size_; + int64_t max_cpb_start_ns_; + int64_t max_cpb_end_ns_; + + int64_t total_compressed_size_; + int64_t total_uncompressed_bits_; + int frames_refreshed_; + int max_frames_refreshed_; + + int max_column_tiles_; + + bool estimate_last_frame_duration_; +}; + +} // namespace vp9_parser + +#endif // LIBWEBM_COMMON_VP9_LEVEL_STATS_H_ diff --git a/common/vp9_level_stats_tests.cc b/common/vp9_level_stats_tests.cc new file mode 100644 index 0000000..0dec071 --- /dev/null +++ b/common/vp9_level_stats_tests.cc @@ -0,0 +1,191 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/vp9_level_stats.h" + +#include <memory> +#include <string> +#include <vector> + +#include "gtest/gtest.h" + +#include "common/hdr_util.h" +#include "common/vp9_header_parser.h" +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" +#include "testing/test_util.h" + +namespace { + +// TODO(fgalligan): Refactor this test with other test files in this directory. +class Vp9LevelStatsTests : public ::testing::Test { + public: + Vp9LevelStatsTests() : is_reader_open_(false) {} + + ~Vp9LevelStatsTests() override { CloseReader(); } + + void CloseReader() { + if (is_reader_open_) { + reader_.Close(); + } + is_reader_open_ = false; + } + + void CreateAndLoadSegment(const std::string& filename, + int expected_doc_type_ver) { + ASSERT_NE(0u, filename.length()); + filename_ = test::GetTestFilePath(filename); + ASSERT_EQ(0, reader_.Open(filename_.c_str())); + is_reader_open_ = true; + pos_ = 0; + mkvparser::EBMLHeader ebml_header; + ebml_header.Parse(&reader_, pos_); + ASSERT_EQ(1, ebml_header.m_version); + ASSERT_EQ(1, ebml_header.m_readVersion); + ASSERT_STREQ("webm", ebml_header.m_docType); + ASSERT_EQ(expected_doc_type_ver, ebml_header.m_docTypeVersion); + ASSERT_EQ(2, ebml_header.m_docTypeReadVersion); + mkvparser::Segment* temp; + ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, temp)); + segment_.reset(temp); + ASSERT_FALSE(HasFailure()); + ASSERT_GE(0, segment_->Load()); + } + + void CreateAndLoadSegment(const std::string& filename) { + CreateAndLoadSegment(filename, 4); + } + + void ProcessTheFrames() { + std::vector<uint8_t> data; + size_t data_len = 0; + const mkvparser::Tracks* const parser_tracks = segment_->GetTracks(); + ASSERT_TRUE(parser_tracks != NULL); + const mkvparser::Cluster* cluster = segment_->GetFirst(); + ASSERT_TRUE(cluster); + + while ((cluster != NULL) && !cluster->EOS()) { + const mkvparser::BlockEntry* block_entry; + long status = cluster->GetFirst(block_entry); // NOLINT + ASSERT_EQ(0, status); + + while ((block_entry != NULL) && !block_entry->EOS()) { + const mkvparser::Block* const block = block_entry->GetBlock(); + ASSERT_TRUE(block != NULL); + const long long trackNum = block->GetTrackNumber(); // NOLINT + const mkvparser::Track* const parser_track = + parser_tracks->GetTrackByNumber( + static_cast<unsigned long>(trackNum)); // NOLINT + ASSERT_TRUE(parser_track != NULL); + const long long track_type = parser_track->GetType(); // NOLINT + + if (track_type == mkvparser::Track::kVideo) { + const int frame_count = block->GetFrameCount(); + const long long time_ns = block->GetTime(cluster); // NOLINT + + for (int i = 0; i < frame_count; ++i) { + const mkvparser::Block::Frame& frame = block->GetFrame(i); + if (static_cast<size_t>(frame.len) > data.size()) { + data.resize(frame.len); + data_len = static_cast<size_t>(frame.len); + } + ASSERT_FALSE(frame.Read(&reader_, &data[0])); + parser_.ParseUncompressedHeader(&data[0], data_len); + stats_.AddFrame(parser_, time_ns); + } + } + + status = cluster->GetNext(block_entry, block_entry); + ASSERT_EQ(0, status); + } + + cluster = segment_->GetNext(cluster); + } + } + + protected: + mkvparser::MkvReader reader_; + bool is_reader_open_; + std::unique_ptr<mkvparser::Segment> segment_; + std::string filename_; + long long pos_; // NOLINT + vp9_parser::Vp9HeaderParser parser_; + vp9_parser::Vp9LevelStats stats_; +}; + +TEST_F(Vp9LevelStatsTests, VideoOnlyFile) { + CreateAndLoadSegment("test_stereo_left_right.webm"); + ProcessTheFrames(); + EXPECT_EQ(256, parser_.width()); + EXPECT_EQ(144, parser_.height()); + EXPECT_EQ(1, parser_.column_tiles()); + EXPECT_EQ(0, parser_.frame_parallel_mode()); + + EXPECT_EQ(11, stats_.GetLevel()); + EXPECT_EQ(479232, stats_.GetMaxLumaSampleRate()); + EXPECT_EQ(36864, stats_.GetMaxLumaPictureSize()); + EXPECT_DOUBLE_EQ(264.03233333333333, stats_.GetAverageBitRate()); + EXPECT_DOUBLE_EQ(147.136, stats_.GetMaxCpbSize()); + EXPECT_DOUBLE_EQ(19.267458404715583, stats_.GetCompressionRatio()); + EXPECT_EQ(1, stats_.GetMaxColumnTiles()); + EXPECT_EQ(11, stats_.GetMinimumAltrefDistance()); + EXPECT_EQ(3, stats_.GetMaxReferenceFrames()); + + EXPECT_TRUE(stats_.estimate_last_frame_duration()); + stats_.set_estimate_last_frame_duration(false); + EXPECT_DOUBLE_EQ(275.512, stats_.GetAverageBitRate()); +} + +TEST_F(Vp9LevelStatsTests, Muxed) { + CreateAndLoadSegment("bbb_480p_vp9_opus_1second.webm", 4); + ProcessTheFrames(); + EXPECT_EQ(854, parser_.width()); + EXPECT_EQ(480, parser_.height()); + EXPECT_EQ(2, parser_.column_tiles()); + EXPECT_EQ(1, parser_.frame_parallel_mode()); + + EXPECT_EQ(30, stats_.GetLevel()); + EXPECT_EQ(9838080, stats_.GetMaxLumaSampleRate()); + EXPECT_EQ(409920, stats_.GetMaxLumaPictureSize()); + EXPECT_DOUBLE_EQ(447.09394572025053, stats_.GetAverageBitRate()); + EXPECT_DOUBLE_EQ(118.464, stats_.GetMaxCpbSize()); + EXPECT_DOUBLE_EQ(241.17670131398313, stats_.GetCompressionRatio()); + EXPECT_EQ(2, stats_.GetMaxColumnTiles()); + EXPECT_EQ(9, stats_.GetMinimumAltrefDistance()); + EXPECT_EQ(3, stats_.GetMaxReferenceFrames()); + + stats_.set_estimate_last_frame_duration(false); + EXPECT_DOUBLE_EQ(468.38413361169108, stats_.GetAverageBitRate()); +} + +TEST_F(Vp9LevelStatsTests, SetDuration) { + CreateAndLoadSegment("test_stereo_left_right.webm"); + ProcessTheFrames(); + const int64_t kDurationNano = 2080000000; // 2.08 seconds + stats_.set_duration(kDurationNano); + EXPECT_EQ(256, parser_.width()); + EXPECT_EQ(144, parser_.height()); + EXPECT_EQ(1, parser_.column_tiles()); + EXPECT_EQ(0, parser_.frame_parallel_mode()); + + EXPECT_EQ(11, stats_.GetLevel()); + EXPECT_EQ(479232, stats_.GetMaxLumaSampleRate()); + EXPECT_EQ(36864, stats_.GetMaxLumaPictureSize()); + EXPECT_DOUBLE_EQ(264.9153846153846, stats_.GetAverageBitRate()); + EXPECT_DOUBLE_EQ(147.136, stats_.GetMaxCpbSize()); + EXPECT_DOUBLE_EQ(19.267458404715583, stats_.GetCompressionRatio()); + EXPECT_EQ(1, stats_.GetMaxColumnTiles()); + EXPECT_EQ(11, stats_.GetMinimumAltrefDistance()); + EXPECT_EQ(3, stats_.GetMaxReferenceFrames()); +} + +} // namespace + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/common/webm_constants.h b/common/webm_constants.h new file mode 100644 index 0000000..a082ce8 --- /dev/null +++ b/common/webm_constants.h @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef LIBWEBM_COMMON_WEBM_CONSTANTS_H_ +#define LIBWEBM_COMMON_WEBM_CONSTANTS_H_ + +namespace libwebm { + +const double kNanosecondsPerSecond = 1000000000.0; +const int kNanosecondsPerSecondi = 1000000000; +const int kNanosecondsPerMillisecond = 1000000; + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_WEBM_CONSTANTS_H_ diff --git a/common/webm_endian.cc b/common/webm_endian.cc new file mode 100644 index 0000000..b1ef7ca --- /dev/null +++ b/common/webm_endian.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "common/webm_endian.h" + +#include <stdint.h> + +namespace { + +// Swaps unsigned 32 bit values to big endian if needed. Returns |value| +// unmodified if architecture is big endian. Returns swapped bytes of |value| +// if architecture is little endian. Returns 0 otherwise. +uint32_t swap32_check_little_endian(uint32_t value) { + // Check endianness. + union { + uint32_t val32; + uint8_t c[4]; + } check; + check.val32 = 0x01234567U; + + // Check for big endian. + if (check.c[3] == 0x67) + return value; + + // Check for not little endian. + if (check.c[0] != 0x67) + return 0; + + return value << 24 | ((value << 8) & 0x0000FF00U) | + ((value >> 8) & 0x00FF0000U) | value >> 24; +} + +// Swaps unsigned 64 bit values to big endian if needed. Returns |value| +// unmodified if architecture is big endian. Returns swapped bytes of |value| +// if architecture is little endian. Returns 0 otherwise. +uint64_t swap64_check_little_endian(uint64_t value) { + // Check endianness. + union { + uint64_t val64; + uint8_t c[8]; + } check; + check.val64 = 0x0123456789ABCDEFULL; + + // Check for big endian. + if (check.c[7] == 0xEF) + return value; + + // Check for not little endian. + if (check.c[0] != 0xEF) + return 0; + + return value << 56 | ((value << 40) & 0x00FF000000000000ULL) | + ((value << 24) & 0x0000FF0000000000ULL) | + ((value << 8) & 0x000000FF00000000ULL) | + ((value >> 8) & 0x00000000FF000000ULL) | + ((value >> 24) & 0x0000000000FF0000ULL) | + ((value >> 40) & 0x000000000000FF00ULL) | value >> 56; +} + +} // namespace + +namespace libwebm { + +uint32_t host_to_bigendian(uint32_t value) { + return swap32_check_little_endian(value); +} + +uint32_t bigendian_to_host(uint32_t value) { + return swap32_check_little_endian(value); +} + +uint64_t host_to_bigendian(uint64_t value) { + return swap64_check_little_endian(value); +} + +uint64_t bigendian_to_host(uint64_t value) { + return swap64_check_little_endian(value); +} + +} // namespace libwebm diff --git a/common/webm_endian.h b/common/webm_endian.h new file mode 100644 index 0000000..351778b --- /dev/null +++ b/common/webm_endian.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef LIBWEBM_COMMON_WEBM_ENDIAN_H_ +#define LIBWEBM_COMMON_WEBM_ENDIAN_H_ + +#include <stdint.h> + +namespace libwebm { + +// Swaps unsigned 32 bit values to big endian if needed. Returns |value| if +// architecture is big endian. Returns big endian value if architecture is +// little endian. Returns 0 otherwise. +uint32_t host_to_bigendian(uint32_t value); + +// Swaps unsigned 32 bit values to little endian if needed. Returns |value| if +// architecture is big endian. Returns little endian value if architecture is +// little endian. Returns 0 otherwise. +uint32_t bigendian_to_host(uint32_t value); + +// Swaps unsigned 64 bit values to big endian if needed. Returns |value| if +// architecture is big endian. Returns big endian value if architecture is +// little endian. Returns 0 otherwise. +uint64_t host_to_bigendian(uint64_t value); + +// Swaps unsigned 64 bit values to little endian if needed. Returns |value| if +// architecture is big endian. Returns little endian value if architecture is +// little endian. Returns 0 otherwise. +uint64_t bigendian_to_host(uint64_t value); + +} // namespace libwebm + +#endif // LIBWEBM_COMMON_WEBM_ENDIAN_H_ diff --git a/common/webmids.h b/common/webmids.h new file mode 100644 index 0000000..fc0c208 --- /dev/null +++ b/common/webmids.h @@ -0,0 +1,193 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef COMMON_WEBMIDS_H_ +#define COMMON_WEBMIDS_H_ + +namespace libwebm { + +enum MkvId { + kMkvEBML = 0x1A45DFA3, + kMkvEBMLVersion = 0x4286, + kMkvEBMLReadVersion = 0x42F7, + kMkvEBMLMaxIDLength = 0x42F2, + kMkvEBMLMaxSizeLength = 0x42F3, + kMkvDocType = 0x4282, + kMkvDocTypeVersion = 0x4287, + kMkvDocTypeReadVersion = 0x4285, + kMkvVoid = 0xEC, + kMkvSignatureSlot = 0x1B538667, + kMkvSignatureAlgo = 0x7E8A, + kMkvSignatureHash = 0x7E9A, + kMkvSignaturePublicKey = 0x7EA5, + kMkvSignature = 0x7EB5, + kMkvSignatureElements = 0x7E5B, + kMkvSignatureElementList = 0x7E7B, + kMkvSignedElement = 0x6532, + // segment + kMkvSegment = 0x18538067, + // Meta Seek Information + kMkvSeekHead = 0x114D9B74, + kMkvSeek = 0x4DBB, + kMkvSeekID = 0x53AB, + kMkvSeekPosition = 0x53AC, + // Segment Information + kMkvInfo = 0x1549A966, + kMkvTimecodeScale = 0x2AD7B1, + kMkvDuration = 0x4489, + kMkvDateUTC = 0x4461, + kMkvTitle = 0x7BA9, + kMkvMuxingApp = 0x4D80, + kMkvWritingApp = 0x5741, + // Cluster + kMkvCluster = 0x1F43B675, + kMkvTimecode = 0xE7, + kMkvPrevSize = 0xAB, + kMkvBlockGroup = 0xA0, + kMkvBlock = 0xA1, + kMkvBlockDuration = 0x9B, + kMkvReferenceBlock = 0xFB, + kMkvLaceNumber = 0xCC, + kMkvSimpleBlock = 0xA3, + kMkvBlockAdditions = 0x75A1, + kMkvBlockMore = 0xA6, + kMkvBlockAddID = 0xEE, + kMkvBlockAdditional = 0xA5, + kMkvDiscardPadding = 0x75A2, + // Track + kMkvTracks = 0x1654AE6B, + kMkvTrackEntry = 0xAE, + kMkvTrackNumber = 0xD7, + kMkvTrackUID = 0x73C5, + kMkvTrackType = 0x83, + kMkvFlagEnabled = 0xB9, + kMkvFlagDefault = 0x88, + kMkvFlagForced = 0x55AA, + kMkvFlagLacing = 0x9C, + kMkvDefaultDuration = 0x23E383, + kMkvMaxBlockAdditionID = 0x55EE, + kMkvName = 0x536E, + kMkvLanguage = 0x22B59C, + kMkvCodecID = 0x86, + kMkvCodecPrivate = 0x63A2, + kMkvCodecName = 0x258688, + kMkvCodecDelay = 0x56AA, + kMkvSeekPreRoll = 0x56BB, + // video + kMkvVideo = 0xE0, + kMkvFlagInterlaced = 0x9A, + kMkvStereoMode = 0x53B8, + kMkvAlphaMode = 0x53C0, + kMkvPixelWidth = 0xB0, + kMkvPixelHeight = 0xBA, + kMkvPixelCropBottom = 0x54AA, + kMkvPixelCropTop = 0x54BB, + kMkvPixelCropLeft = 0x54CC, + kMkvPixelCropRight = 0x54DD, + kMkvDisplayWidth = 0x54B0, + kMkvDisplayHeight = 0x54BA, + kMkvDisplayUnit = 0x54B2, + kMkvAspectRatioType = 0x54B3, + kMkvColourSpace = 0x2EB524, + kMkvFrameRate = 0x2383E3, + // end video + // colour + kMkvColour = 0x55B0, + kMkvMatrixCoefficients = 0x55B1, + kMkvBitsPerChannel = 0x55B2, + kMkvChromaSubsamplingHorz = 0x55B3, + kMkvChromaSubsamplingVert = 0x55B4, + kMkvCbSubsamplingHorz = 0x55B5, + kMkvCbSubsamplingVert = 0x55B6, + kMkvChromaSitingHorz = 0x55B7, + kMkvChromaSitingVert = 0x55B8, + kMkvRange = 0x55B9, + kMkvTransferCharacteristics = 0x55BA, + kMkvPrimaries = 0x55BB, + kMkvMaxCLL = 0x55BC, + kMkvMaxFALL = 0x55BD, + // mastering metadata + kMkvMasteringMetadata = 0x55D0, + kMkvPrimaryRChromaticityX = 0x55D1, + kMkvPrimaryRChromaticityY = 0x55D2, + kMkvPrimaryGChromaticityX = 0x55D3, + kMkvPrimaryGChromaticityY = 0x55D4, + kMkvPrimaryBChromaticityX = 0x55D5, + kMkvPrimaryBChromaticityY = 0x55D6, + kMkvWhitePointChromaticityX = 0x55D7, + kMkvWhitePointChromaticityY = 0x55D8, + kMkvLuminanceMax = 0x55D9, + kMkvLuminanceMin = 0x55DA, + // end mastering metadata + // end colour + // projection + kMkvProjection = 0x7670, + kMkvProjectionType = 0x7671, + kMkvProjectionPrivate = 0x7672, + kMkvProjectionPoseYaw = 0x7673, + kMkvProjectionPosePitch = 0x7674, + kMkvProjectionPoseRoll = 0x7675, + // end projection + // audio + kMkvAudio = 0xE1, + kMkvSamplingFrequency = 0xB5, + kMkvOutputSamplingFrequency = 0x78B5, + kMkvChannels = 0x9F, + kMkvBitDepth = 0x6264, + // end audio + // ContentEncodings + kMkvContentEncodings = 0x6D80, + kMkvContentEncoding = 0x6240, + kMkvContentEncodingOrder = 0x5031, + kMkvContentEncodingScope = 0x5032, + kMkvContentEncodingType = 0x5033, + kMkvContentCompression = 0x5034, + kMkvContentCompAlgo = 0x4254, + kMkvContentCompSettings = 0x4255, + kMkvContentEncryption = 0x5035, + kMkvContentEncAlgo = 0x47E1, + kMkvContentEncKeyID = 0x47E2, + kMkvContentSignature = 0x47E3, + kMkvContentSigKeyID = 0x47E4, + kMkvContentSigAlgo = 0x47E5, + kMkvContentSigHashAlgo = 0x47E6, + kMkvContentEncAESSettings = 0x47E7, + kMkvAESSettingsCipherMode = 0x47E8, + kMkvAESSettingsCipherInitData = 0x47E9, + // end ContentEncodings + // Cueing Data + kMkvCues = 0x1C53BB6B, + kMkvCuePoint = 0xBB, + kMkvCueTime = 0xB3, + kMkvCueTrackPositions = 0xB7, + kMkvCueTrack = 0xF7, + kMkvCueClusterPosition = 0xF1, + kMkvCueBlockNumber = 0x5378, + // Chapters + kMkvChapters = 0x1043A770, + kMkvEditionEntry = 0x45B9, + kMkvChapterAtom = 0xB6, + kMkvChapterUID = 0x73C4, + kMkvChapterStringUID = 0x5654, + kMkvChapterTimeStart = 0x91, + kMkvChapterTimeEnd = 0x92, + kMkvChapterDisplay = 0x80, + kMkvChapString = 0x85, + kMkvChapLanguage = 0x437C, + kMkvChapCountry = 0x437E, + // Tags + kMkvTags = 0x1254C367, + kMkvTag = 0x7373, + kMkvSimpleTag = 0x67C8, + kMkvTagName = 0x45A3, + kMkvTagString = 0x4487 +}; + +} // namespace libwebm + +#endif // COMMON_WEBMIDS_H_ diff --git a/dumpvtt.cc b/dumpvtt.cc new file mode 100644 index 0000000..472da52 --- /dev/null +++ b/dumpvtt.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include <cstdio> +#include <cstdlib> +#include "webvtt/vttreader.h" +#include "webvtt/webvttparser.h" + +int main(int argc, const char* argv[]) { + if (argc != 2) { + fprintf(stdout, "usage: dumpvtt <vtt file>\n"); + return EXIT_SUCCESS; + } + + libwebvtt::VttReader reader; + const char* const filename = argv[1]; + + if (int e = reader.Open(filename)) { + (void)e; + fprintf(stderr, "open failed\n"); + return EXIT_FAILURE; + } + + libwebvtt::Parser parser(&reader); + + if (int e = parser.Init()) { + (void)e; + fprintf(stderr, "parser init failed\n"); + return EXIT_FAILURE; + } + + for (libwebvtt::Cue cue;;) { + const int e = parser.Parse(&cue); + + if (e < 0) { // error + fprintf(stderr, "error parsing cue\n"); + return EXIT_FAILURE; + } + + if (e > 0) // EOF + return EXIT_SUCCESS; + + fprintf(stdout, "cue identifier: \"%s\"\n", cue.identifier.c_str()); + + const libwebvtt::Time& st = cue.start_time; + fprintf(stdout, "cue start time: \"HH=%i MM=%i SS=%i SSS=%i\"\n", st.hours, + st.minutes, st.seconds, st.milliseconds); + + const libwebvtt::Time& sp = cue.stop_time; + fprintf(stdout, "cue stop time: \"HH=%i MM=%i SS=%i SSS=%i\"\n", sp.hours, + sp.minutes, sp.seconds, sp.milliseconds); + + { + typedef libwebvtt::Cue::settings_t::const_iterator iter_t; + iter_t i = cue.settings.begin(); + const iter_t j = cue.settings.end(); + + if (i == j) { + fprintf(stdout, "cue setting: <no settings present>\n"); + } else { + while (i != j) { + const libwebvtt::Setting& setting = *i++; + fprintf(stdout, "cue setting: name=%s value=%s\n", + setting.name.c_str(), setting.value.c_str()); + } + } + } + + { + typedef libwebvtt::Cue::payload_t::const_iterator iter_t; + iter_t i = cue.payload.begin(); + const iter_t j = cue.payload.end(); + + int idx = 1; + while (i != j) { + const std::string& payload = *i++; + const char* const payload_str = payload.c_str(); + fprintf(stdout, "cue payload[%i]: \"%s\"\n", idx, payload_str); + ++idx; + } + } + + fprintf(stdout, "\n"); + fflush(stdout); + } +} diff --git a/hdr_util.hpp b/hdr_util.hpp new file mode 100644 index 0000000..7abcb3a --- /dev/null +++ b/hdr_util.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_HDR_UTIL_HPP_ +#define LIBWEBM_HDR_UTIL_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "common/hdr_util.h" + +#endif // LIBWEBM_HDR_UTIL_HPP_ diff --git a/iosbuild.sh b/iosbuild.sh new file mode 100755 index 0000000..a183af3 --- /dev/null +++ b/iosbuild.sh @@ -0,0 +1,207 @@ +#!/bin/sh +## +## Copyright (c) 2015 The WebM project authors. All Rights Reserved. +## +## Use of this source code is governed by a BSD-style license +## that can be found in the LICENSE file in the root of the source +## tree. An additional intellectual property rights grant can be found +## in the file PATENTS. All contributing project authors may +## be found in the AUTHORS file in the root of the source tree. +## +## This script generates 'WebM.framework'. An iOS app can mux/demux WebM +## container files by including 'WebM.framework'. +## +## Run ./iosbuild.sh to generate 'WebM.framework'. By default the framework +## bundle will be created in a directory called framework. Use --out-dir to +## change the output directory. +## +## This script is based on iosbuild.sh from the libwebp project. +. $(dirname $0)/common/common.sh + +# Trap function. Cleans up build output. +cleanup() { + local readonly res=$? + cd "${ORIG_PWD}" + + for dir in ${LIBDIRS}; do + if [ -d "${dir}" ]; then + rm -rf "${dir}" + fi + done + + if [ $res -ne 0 ]; then + elog "build exited with error ($res)" + fi +} + +trap cleanup EXIT + +check_dir libwebm + +iosbuild_usage() { +cat << EOF + Usage: ${0##*/} [arguments] + --help: Display this message and exit. + --out-dir: Override output directory (default is ${OUTDIR}). + --show-build-output: Show output from each library build. + --verbose: Output information about the environment and each stage of the + build. +EOF +} + +# Extract the latest SDK version from the final field of the form: iphoneosX.Y +readonly SDK=$(xcodebuild -showsdks \ + | grep iphoneos | sort | tail -n 1 | awk '{print substr($NF, 9)}' +) + +# Extract Xcode version. +readonly XCODE=$(xcodebuild -version | grep Xcode | cut -d " " -f2) +if [ -z "${XCODE}" ]; then + echo "Xcode not available" + exit 1 +fi + +# Add iPhoneOS-V6 to the list of platforms below if you need armv6 support. +# Note that iPhoneOS-V6 support is not available with the iOS6 SDK. +readonly INCLUDES="common/file_util.h + common/hdr_util.h + common/webmids.h + mkvmuxer/mkvmuxer.h + mkvmuxer/mkvmuxertypes.h + mkvmuxer/mkvmuxerutil.h + mkvmuxer/mkvwriter.h + mkvparser/mkvparser.h + mkvparser/mkvreader.h" +readonly PLATFORMS="iPhoneSimulator + iPhoneSimulator64 + iPhoneOS-V7 + iPhoneOS-V7s + iPhoneOS-V7-arm64" +readonly TARGETDIR="WebM.framework" +readonly DEVELOPER="$(xcode-select --print-path)" +readonly PLATFORMSROOT="${DEVELOPER}/Platforms" +readonly LIPO="$(xcrun -sdk iphoneos${SDK} -find lipo)" +LIBLIST="" +OPT_FLAGS="-DNDEBUG -O3" +readonly SDK_MAJOR_VERSION="$(echo ${SDK} | awk -F '.' '{ print $1 }')" + +if [ -z "${SDK_MAJOR_VERSION}" ]; then + elog "iOS SDK not available" + exit 1 +elif [ "${SDK_MAJOR_VERSION}" -lt "6" ]; then + elog "You need iOS SDK version 6 or above" + exit 1 +else + vlog "iOS SDK Version ${SDK}" +fi + + +# Parse the command line. +while [ -n "$1" ]; do + case "$1" in + --help) + iosbuild_usage + exit + ;; + --out-dir) + OUTDIR="$2" + shift + ;; + --enable-debug) + OPT_FLAGS="-g" + ;; + --show-build-output) + devnull= + ;; + --verbose) + VERBOSE=yes + ;; + *) + iosbuild_usage + exit 1 + ;; + esac + shift +done + +readonly OPT_FLAGS="${OPT_FLAGS}" +readonly OUTDIR="${OUTDIR:-framework}" + +if [ "${VERBOSE}" = "yes" ]; then +cat << EOF + OUTDIR=${OUTDIR} + INCLUDES=${INCLUDES} + PLATFORMS=${PLATFORMS} + TARGETDIR=${TARGETDIR} + DEVELOPER=${DEVELOPER} + LIPO=${LIPO} + OPT_FLAGS=${OPT_FLAGS} + ORIG_PWD=${ORIG_PWD} +EOF +fi + +rm -rf "${OUTDIR}/${TARGETDIR}" +mkdir -p "${OUTDIR}/${TARGETDIR}/Headers/" + +for PLATFORM in ${PLATFORMS}; do + ARCH2="" + if [ "${PLATFORM}" = "iPhoneOS-V7-arm64" ]; then + PLATFORM="iPhoneOS" + ARCH="aarch64" + ARCH2="arm64" + elif [ "${PLATFORM}" = "iPhoneOS-V7s" ]; then + PLATFORM="iPhoneOS" + ARCH="armv7s" + elif [ "${PLATFORM}" = "iPhoneOS-V7" ]; then + PLATFORM="iPhoneOS" + ARCH="armv7" + elif [ "${PLATFORM}" = "iPhoneOS-V6" ]; then + PLATFORM="iPhoneOS" + ARCH="armv6" + elif [ "${PLATFORM}" = "iPhoneSimulator64" ]; then + PLATFORM="iPhoneSimulator" + ARCH="x86_64" + else + ARCH="i386" + fi + + LIBDIR="${OUTDIR}/${PLATFORM}-${SDK}-${ARCH}" + LIBDIRS="${LIBDIRS} ${LIBDIR}" + LIBFILE="${LIBDIR}/libwebm.a" + eval mkdir -p "${LIBDIR}" ${devnull} + + DEVROOT="${DEVELOPER}/Toolchains/XcodeDefault.xctoolchain" + SDKROOT="${PLATFORMSROOT}/" + SDKROOT="${SDKROOT}${PLATFORM}.platform/Developer/SDKs/${PLATFORM}${SDK}.sdk/" + CXXFLAGS="-arch ${ARCH2:-${ARCH}} -isysroot ${SDKROOT} ${OPT_FLAGS} + -miphoneos-version-min=6.0" + + # enable bitcode if available + if [ "${SDK_MAJOR_VERSION}" -gt 8 ]; then + CXXFLAGS="${CXXFLAGS} -fembed-bitcode" + fi + + # Build using the legacy makefile (instead of generating via cmake). + eval make -f Makefile.unix libwebm.a CXXFLAGS=\"${CXXFLAGS}\" ${devnull} + + # copy lib and add it to LIBLIST. + eval cp libwebm.a "${LIBFILE}" ${devnull} + LIBLIST="${LIBLIST} ${LIBFILE}" + + # clean build so we can go again. + eval make -f Makefile.unix clean ${devnull} +done + +# create include sub dirs in framework dir. +readonly framework_header_dir="${OUTDIR}/${TARGETDIR}/Headers" +readonly framework_header_sub_dirs="common mkvmuxer mkvparser" +for dir in ${framework_header_sub_dirs}; do + mkdir "${framework_header_dir}/${dir}" +done + +for header_file in ${INCLUDES}; do + eval cp -p ${header_file} "${framework_header_dir}/${header_file}" ${devnull} +done + +eval ${LIPO} -create ${LIBLIST} -output "${OUTDIR}/${TARGETDIR}/WebM" ${devnull} +echo "Succesfully built ${TARGETDIR} in ${OUTDIR}." diff --git a/m2ts/tests/webm2pes_tests.cc b/m2ts/tests/webm2pes_tests.cc new file mode 100644 index 0000000..664856f --- /dev/null +++ b/m2ts/tests/webm2pes_tests.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "m2ts/webm2pes.h" + +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <limits> +#include <string> +#include <vector> + +#include "gtest/gtest.h" + +#include "common/file_util.h" +#include "common/libwebm_util.h" +#include "m2ts/vpxpes_parser.h" +#include "testing/test_util.h" + +namespace { + +class Webm2PesTests : public ::testing::Test { + public: + // Constants for validating known values from input data. + const std::uint8_t kMinVideoStreamId = 0xE0; + const std::uint8_t kMaxVideoStreamId = 0xEF; + const int kPesHeaderSize = 6; + const int kPesOptionalHeaderStartOffset = kPesHeaderSize; + const int kPesOptionalHeaderSize = 9; + const int kPesOptionalHeaderMarkerValue = 0x2; + const int kWebm2PesOptHeaderRemainingSize = 6; + const int kBcmvHeaderSize = 10; + + Webm2PesTests() = default; + ~Webm2PesTests() = default; + + void CreateAndLoadTestInput() { + libwebm::Webm2Pes converter(input_file_name_, temp_file_name_.name()); + ASSERT_TRUE(converter.ConvertToFile()); + ASSERT_TRUE(parser_.Open(pes_file_name())); + } + + bool VerifyPacketStartCode(const libwebm::VpxPesParser::PesHeader& header) { + // PES packets all start with the byte sequence 0x0 0x0 0x1. + if (header.start_code[0] != 0 || header.start_code[1] != 0 || + header.start_code[2] != 1) { + return false; + } + return true; + } + + const std::string& pes_file_name() const { return temp_file_name_.name(); } + libwebm::VpxPesParser* parser() { return &parser_; } + + private: + const libwebm::TempFileDeleter temp_file_name_; + const std::string input_file_name_ = + test::GetTestFilePath("bbb_480p_vp9_opus_1second.webm"); + libwebm::VpxPesParser parser_; +}; + +TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); } + +TEST_F(Webm2PesTests, CanParseFirstPacket) { + CreateAndLoadTestInput(); + libwebm::VpxPesParser::PesHeader header; + libwebm::VideoFrame frame; + ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame)); + EXPECT_TRUE(VerifyPacketStartCode(header)); + + // 9 bytes: PES optional header + // 10 bytes: BCMV Header + // 83 bytes: frame + // 102 bytes total in packet length field: + const std::size_t kPesPayloadLength = 102; + EXPECT_EQ(kPesPayloadLength, header.packet_length); + + EXPECT_GE(header.stream_id, kMinVideoStreamId); + EXPECT_LE(header.stream_id, kMaxVideoStreamId); + + // Test PesOptionalHeader values. + EXPECT_EQ(kPesOptionalHeaderMarkerValue, header.opt_header.marker); + EXPECT_EQ(kWebm2PesOptHeaderRemainingSize, header.opt_header.remaining_size); + EXPECT_EQ(0, header.opt_header.scrambling); + EXPECT_EQ(0, header.opt_header.priority); + EXPECT_EQ(0, header.opt_header.data_alignment); + EXPECT_EQ(0, header.opt_header.copyright); + EXPECT_EQ(0, header.opt_header.original); + EXPECT_EQ(1, header.opt_header.has_pts); + EXPECT_EQ(0, header.opt_header.has_dts); + EXPECT_EQ(0, header.opt_header.unused_fields); + + // Test the BCMV header. + // Note: The length field of the BCMV header includes its own length. + const std::size_t kBcmvBaseLength = 10; + const std::size_t kFirstFrameLength = 83; + const libwebm::VpxPesParser::BcmvHeader kFirstBcmvHeader(kFirstFrameLength + + kBcmvBaseLength); + EXPECT_TRUE(header.bcmv_header.Valid()); + EXPECT_EQ(kFirstBcmvHeader, header.bcmv_header); + + // Parse the next packet to confirm correct parse and consumption of payload. + EXPECT_TRUE(parser()->ParseNextPacket(&header, &frame)); +} + +TEST_F(Webm2PesTests, CanMuxLargeBuffers) { + const std::size_t kBufferSize = 100 * 1024; + const std::int64_t kFakeTimestamp = libwebm::kNanosecondsPerSecond; + libwebm::VideoFrame fake_frame(kFakeTimestamp, libwebm::VideoFrame::kVP9); + ASSERT_TRUE(fake_frame.Init(kBufferSize)); + std::memset(fake_frame.buffer().data.get(), 0x80, kBufferSize); + ASSERT_TRUE(fake_frame.SetBufferLength(kBufferSize)); + libwebm::PacketDataBuffer pes_packet_buffer; + ASSERT_TRUE( + libwebm::Webm2Pes::WritePesPacket(fake_frame, &pes_packet_buffer)); + + // TODO(tomfinegan): Change VpxPesParser so it can read from a buffer, and get + // rid of this extra step. + libwebm::FilePtr pes_file(std::fopen(pes_file_name().c_str(), "wb"), + libwebm::FILEDeleter()); + ASSERT_EQ(pes_packet_buffer.size(), + fwrite(&pes_packet_buffer[0], 1, pes_packet_buffer.size(), + pes_file.get())); + fclose(pes_file.get()); + pes_file.release(); + + libwebm::VpxPesParser parser; + ASSERT_TRUE(parser.Open(pes_file_name())); + libwebm::VpxPesParser::PesHeader header; + libwebm::VideoFrame parsed_frame; + ASSERT_TRUE(parser.ParseNextPacket(&header, &parsed_frame)); + EXPECT_EQ(fake_frame.nanosecond_pts(), parsed_frame.nanosecond_pts()); + EXPECT_EQ(fake_frame.buffer().length, parsed_frame.buffer().length); + EXPECT_EQ(0, std::memcmp(fake_frame.buffer().data.get(), + parsed_frame.buffer().data.get(), kBufferSize)); +} + +TEST_F(Webm2PesTests, ParserConsumesAllInput) { + CreateAndLoadTestInput(); + libwebm::VpxPesParser::PesHeader header; + libwebm::VideoFrame frame; + while (parser()->ParseNextPacket(&header, &frame) == true) { + EXPECT_TRUE(VerifyPacketStartCode(header)); + } + EXPECT_EQ(0, parser()->BytesAvailable()); +} + +} // namespace + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/m2ts/vpxpes2ts.cc b/m2ts/vpxpes2ts.cc new file mode 100644 index 0000000..7684b56 --- /dev/null +++ b/m2ts/vpxpes2ts.cc @@ -0,0 +1,217 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "m2ts/vpxpes2ts.h" + +#include <algorithm> +#include <cstdint> +#include <cstdio> +#include <vector> + +namespace libwebm { +// TODO(tomfinegan): Dedupe this and PesHeaderField. +// Stores a value and its size in bits for writing into a MPEG2 TS Header. +// Maximum size is 64 bits. Users may call the Check() method to perform minimal +// validation (size > 0 and <= 64). +struct TsHeaderField { + TsHeaderField(std::uint64_t value, std::uint32_t size_in_bits, + std::uint8_t byte_index, std::uint8_t bits_to_shift) + : bits(value), + num_bits(size_in_bits), + index(byte_index), + shift(bits_to_shift) {} + TsHeaderField() = delete; + TsHeaderField(const TsHeaderField&) = default; + TsHeaderField(TsHeaderField&&) = default; + ~TsHeaderField() = default; + bool Check() const { + return num_bits > 0 && num_bits <= 64 && shift >= 0 && shift < 64; + } + + // Value to be stored in the field. + std::uint64_t bits; + + // Number of bits in the value. + const int num_bits; + + // Index into the header for the byte in which |bits| will be written. + const std::uint8_t index; + + // Number of bits to left shift value before or'ing. Ignored for whole bytes. + const int shift; +}; + +// Data storage for MPEG2 Transport Stream headers. +// https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet +struct TsHeader { + TsHeader(bool payload_start, bool adaptation_flag, std::uint8_t counter) + : is_payload_start(payload_start), + has_adaptation(adaptation_flag), + counter_value(counter) {} + TsHeader() = delete; + TsHeader(const TsHeader&) = default; + TsHeader(TsHeader&&) = default; + ~TsHeader() = default; + + void Write(PacketDataBuffer* buffer) const; + + // Indicates the packet is the beginning of a new fragmented payload. + const bool is_payload_start; + + // Indicates the packet contains an adaptation field. + const bool has_adaptation; + + // The sync byte is the bit pattern of 0x47 (ASCII char 'G'). + const std::uint8_t kTsHeaderSyncByte = 0x47; + const std::uint8_t sync_byte = kTsHeaderSyncByte; + + // Value for |continuity_counter|. Used to detect gaps when demuxing. + const std::uint8_t counter_value; + + // Set when FEC is impossible. Always 0. + const TsHeaderField transport_error_indicator = TsHeaderField(0, 1, 1, 7); + + // This MPEG2 TS header is the start of a new payload (aka PES packet). + const TsHeaderField payload_unit_start_indicator = + TsHeaderField(is_payload_start ? 1 : 0, 1, 1, 6); + + // Set when the current packet has a higher priority than other packets with + // the same PID. Always 0 for VPX. + const TsHeaderField transport_priority = TsHeaderField(0, 1, 1, 5); + + // https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet_Identifier_.28PID.29 + // 0x0020-0x1FFA May be assigned as needed to Program Map Tables, elementary + // streams and other data tables. + // Note: Though we hard code to 0x20, this value is actually 13 bits-- the + // buffer for the header is always set to 0, so it doesn't matter in practice. + const TsHeaderField pid = TsHeaderField(0x20, 8, 2, 0); + + // Indicates scrambling key. Unused; always 0. + const TsHeaderField scrambling_control = TsHeaderField(0, 2, 3, 6); + + // Adaptation field flag. Unused; always 0. + // TODO(tomfinegan): Not sure this is OK. Might need to add support for + // writing the Adaptation Field. + const TsHeaderField adaptation_field_flag = + TsHeaderField(has_adaptation ? 1 : 0, 1, 3, 5); + + // Payload flag. All output packets created here have payloads. Always 1. + const TsHeaderField payload_flag = TsHeaderField(1, 1, 3, 4); + + // Continuity counter. Two bit field that is incremented for every packet. + const TsHeaderField continuity_counter = + TsHeaderField(counter_value, 4, 3, 3); +}; + +void TsHeader::Write(PacketDataBuffer* buffer) const { + std::uint8_t* byte = &(*buffer)[0]; + *byte = sync_byte; + + *++byte = 0; + *byte |= transport_error_indicator.bits << transport_error_indicator.shift; + *byte |= payload_unit_start_indicator.bits + << payload_unit_start_indicator.shift; + *byte |= transport_priority.bits << transport_priority.shift; + + *++byte = pid.bits & 0xff; + + *++byte = 0; + *byte |= scrambling_control.bits << scrambling_control.shift; + *byte |= adaptation_field_flag.bits << adaptation_field_flag.shift; + *byte |= payload_flag.bits << payload_flag.shift; + *byte |= continuity_counter.bits; // last 4 bits. +} + +bool VpxPes2Ts::ConvertToFile() { + output_file_ = FilePtr(fopen(output_file_name_.c_str(), "wb"), FILEDeleter()); + if (output_file_ == nullptr) { + std::fprintf(stderr, "VpxPes2Ts: Cannot open %s for output.\n", + output_file_name_.c_str()); + return false; + } + pes_converter_.reset(new Webm2Pes(input_file_name_, this)); + if (pes_converter_ == nullptr) { + std::fprintf(stderr, "VpxPes2Ts: Out of memory.\n"); + return false; + } + return pes_converter_->ConvertToPacketReceiver(); +} + +bool VpxPes2Ts::ReceivePacket(const PacketDataBuffer& packet_data) { + const int kTsHeaderSize = 4; + const int kTsPayloadSize = 184; + const int kTsPacketSize = kTsHeaderSize + kTsPayloadSize; + int bytes_to_packetize = static_cast<int>(packet_data.size()); + std::uint8_t continuity_counter = 0; + std::size_t read_pos = 0; + + ts_buffer_.reserve(kTsPacketSize); + + while (bytes_to_packetize > 0) { + if (continuity_counter > 0xf) + continuity_counter = 0; + + // Calculate payload size (need to know if we'll have to pad with an empty + // adaptation field). + int payload_size = std::min(bytes_to_packetize, kTsPayloadSize); + + // Write the TS header. + const TsHeader header( + bytes_to_packetize == static_cast<int>(packet_data.size()), + payload_size != kTsPayloadSize, continuity_counter); + header.Write(&ts_buffer_); + int write_pos = kTsHeaderSize; + + // (pre)Pad payload with an empty adaptation field. All packets must be + // |kTsPacketSize| (188). + if (payload_size < kTsPayloadSize) { + // We need at least 2 bytes to write an empty adaptation field. + if (payload_size == (kTsPayloadSize - 1)) { + payload_size--; + } + + // Padding adaptation field: + // 8 bits: number of adaptation field bytes following this byte. + // 8 bits: unused (in this program) flags. + // This is followed by a run of 0xff to reach |kTsPayloadSize| (184) + // bytes. + const int pad_size = kTsPayloadSize - payload_size - 1 - 1; + ts_buffer_[write_pos++] = pad_size + 1; + ts_buffer_[write_pos++] = 0; + + const std::uint8_t kStuffingByte = 0xff; + for (int i = 0; i < pad_size; ++i) { + ts_buffer_[write_pos++] = kStuffingByte; + } + } + + for (int i = 0; i < payload_size; ++i) { + ts_buffer_[write_pos++] = packet_data[read_pos++]; + } + + bytes_to_packetize -= payload_size; + continuity_counter++; + + if (write_pos != kTsPacketSize) { + fprintf(stderr, "VpxPes2Ts: Invalid packet length.\n"); + return false; + } + + // Write contents of |ts_buffer_| to |output_file_|. + // TODO(tomfinegan): Writing 188 bytes at a time isn't exactly efficient... + // Fix me. + if (static_cast<int>(std::fwrite(&ts_buffer_[0], 1, kTsPacketSize, + output_file_.get())) != kTsPacketSize) { + std::fprintf(stderr, "VpxPes2Ts: TS packet write failed.\n"); + return false; + } + } + + return true; +} + +} // namespace libwebm diff --git a/m2ts/vpxpes2ts.h b/m2ts/vpxpes2ts.h new file mode 100644 index 0000000..2609a1f --- /dev/null +++ b/m2ts/vpxpes2ts.h @@ -0,0 +1,45 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_M2TS_VPXPES2TS_H_ +#define LIBWEBM_M2TS_VPXPES2TS_H_ + +#include <memory> +#include <string> + +#include "common/libwebm_util.h" +#include "m2ts/webm2pes.h" + +namespace libwebm { + +class VpxPes2Ts : public PacketReceiverInterface { + public: + VpxPes2Ts(const std::string& input_file_name, + const std::string& output_file_name) + : input_file_name_(input_file_name), + output_file_name_(output_file_name) {} + virtual ~VpxPes2Ts() = default; + VpxPes2Ts() = delete; + VpxPes2Ts(const VpxPes2Ts&) = delete; + VpxPes2Ts(VpxPes2Ts&&) = delete; + + bool ConvertToFile(); + + private: + bool ReceivePacket(const PacketDataBuffer& packet_data) override; + + const std::string input_file_name_; + const std::string output_file_name_; + + FilePtr output_file_; + std::unique_ptr<Webm2Pes> pes_converter_; + PacketDataBuffer ts_buffer_; +}; + +} // namespace libwebm + +#endif // LIBWEBM_M2TS_VPXPES2TS_H_ diff --git a/m2ts/vpxpes2ts_main.cc b/m2ts/vpxpes2ts_main.cc new file mode 100644 index 0000000..435d805 --- /dev/null +++ b/m2ts/vpxpes2ts_main.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "m2ts/vpxpes2ts.h" + +#include <cstdio> +#include <cstdlib> +#include <string> + +namespace { + +void Usage(const char* argv[]) { + printf("Usage: %s <WebM file> <output file>", argv[0]); +} + +} // namespace + +int main(int argc, const char* argv[]) { + if (argc < 3) { + Usage(argv); + return EXIT_FAILURE; + } + + const std::string input_path = argv[1]; + const std::string output_path = argv[2]; + + libwebm::VpxPes2Ts converter(input_path, output_path); + return converter.ConvertToFile() == true ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/m2ts/vpxpes_parser.cc b/m2ts/vpxpes_parser.cc new file mode 100644 index 0000000..4f6fe5c --- /dev/null +++ b/m2ts/vpxpes_parser.cc @@ -0,0 +1,409 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "vpxpes_parser.h" + +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <limits> +#include <vector> + +#include "common/file_util.h" + +namespace libwebm { + +VpxPesParser::BcmvHeader::BcmvHeader(std::uint32_t len) : length(len) { + id[0] = 'B'; + id[1] = 'C'; + id[2] = 'M'; + id[3] = 'V'; +} + +bool VpxPesParser::BcmvHeader::operator==(const BcmvHeader& other) const { + return (other.length == length && other.id[0] == id[0] && + other.id[1] == id[1] && other.id[2] == id[2] && other.id[3] == id[3]); +} + +bool VpxPesParser::BcmvHeader::Valid() const { + return (length > 0 && id[0] == 'B' && id[1] == 'C' && id[2] == 'M' && + id[3] == 'V'); +} + +// TODO(tomfinegan): Break Open() into separate functions. One that opens the +// file, and one that reads one packet at a time. As things are files larger +// than the maximum availble memory for the current process cannot be loaded. +bool VpxPesParser::Open(const std::string& pes_file) { + pes_file_size_ = static_cast<size_t>(libwebm::GetFileSize(pes_file)); + if (pes_file_size_ <= 0) + return false; + pes_file_data_.reserve(static_cast<size_t>(pes_file_size_)); + libwebm::FilePtr file = libwebm::FilePtr(std::fopen(pes_file.c_str(), "rb"), + libwebm::FILEDeleter()); + int byte; + while ((byte = fgetc(file.get())) != EOF) { + pes_file_data_.push_back(static_cast<std::uint8_t>(byte)); + } + + if (!feof(file.get()) || ferror(file.get()) || + pes_file_size_ != pes_file_data_.size()) { + return false; + } + + read_pos_ = 0; + parse_state_ = kFindStartCode; + return true; +} + +bool VpxPesParser::VerifyPacketStartCode() const { + if (read_pos_ + 2 > pes_file_data_.size()) + return false; + + // PES packets all start with the byte sequence 0x0 0x0 0x1. + if (pes_file_data_[read_pos_] != 0 || pes_file_data_[read_pos_ + 1] != 0 || + pes_file_data_[read_pos_ + 2] != 1) { + return false; + } + + return true; +} + +bool VpxPesParser::ReadStreamId(std::uint8_t* stream_id) const { + if (!stream_id || BytesAvailable() < 4) + return false; + + *stream_id = pes_file_data_[read_pos_ + 3]; + return true; +} + +bool VpxPesParser::ReadPacketLength(std::uint16_t* packet_length) const { + if (!packet_length || BytesAvailable() < 6) + return false; + + // Read and byte swap 16 bit big endian length. + *packet_length = + (pes_file_data_[read_pos_ + 4] << 8) | pes_file_data_[read_pos_ + 5]; + + return true; +} + +bool VpxPesParser::ParsePesHeader(PesHeader* header) { + if (!header || parse_state_ != kParsePesHeader) + return false; + + if (!VerifyPacketStartCode()) + return false; + + std::size_t pos = read_pos_; + for (auto& a : header->start_code) { + a = pes_file_data_[pos++]; + } + + // PES Video stream IDs start at E0. + if (!ReadStreamId(&header->stream_id)) + return false; + + if (header->stream_id < kMinVideoStreamId || + header->stream_id > kMaxVideoStreamId) + return false; + + if (!ReadPacketLength(&header->packet_length)) + return false; + + read_pos_ += kPesHeaderSize; + parse_state_ = kParsePesOptionalHeader; + return true; +} + +// TODO(tomfinegan): Make these masks constants. +bool VpxPesParser::ParsePesOptionalHeader(PesOptionalHeader* header) { + if (!header || parse_state_ != kParsePesOptionalHeader || + read_pos_ >= pes_file_size_) { + return false; + } + + std::size_t consumed = 0; + PacketData poh_buffer; + if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_], + kPesOptionalHeaderSize, + &poh_buffer, &consumed)) { + return false; + } + + std::size_t offset = 0; + header->marker = (poh_buffer[offset] & 0x80) >> 6; + header->scrambling = (poh_buffer[offset] & 0x30) >> 4; + header->priority = (poh_buffer[offset] & 0x8) >> 3; + header->data_alignment = (poh_buffer[offset] & 0xc) >> 2; + header->copyright = (poh_buffer[offset] & 0x2) >> 1; + header->original = poh_buffer[offset] & 0x1; + offset++; + + header->has_pts = (poh_buffer[offset] & 0x80) >> 7; + header->has_dts = (poh_buffer[offset] & 0x40) >> 6; + header->unused_fields = poh_buffer[offset] & 0x3f; + offset++; + + header->remaining_size = poh_buffer[offset]; + if (header->remaining_size != + static_cast<int>(kWebm2PesOptHeaderRemainingSize)) + return false; + + size_t bytes_left = header->remaining_size; + offset++; + + if (header->has_pts) { + // Read PTS markers. Format: + // PTS: 5 bytes + // 4 bits (flag: PTS present, but no DTS): 0x2 ('0010') + // 36 bits (90khz PTS): + // top 3 bits + // marker ('1') + // middle 15 bits + // marker ('1') + // bottom 15 bits + // marker ('1') + // TODO(tomfinegan): read/store the timestamp. + header->pts_dts_flag = (poh_buffer[offset] & 0x20) >> 4; + // Check the marker bits. + if ((poh_buffer[offset + 0] & 1) != 1 || + (poh_buffer[offset + 2] & 1) != 1 || + (poh_buffer[offset + 4] & 1) != 1) { + return false; + } + + header->pts = (poh_buffer[offset] & 0xe) << 29 | + ((ReadUint16(&poh_buffer[offset + 1]) & ~1) << 14) | + (ReadUint16(&poh_buffer[offset + 3]) >> 1); + offset += 5; + bytes_left -= 5; + } + + // Validate stuffing byte(s). + for (size_t i = 0; i < bytes_left; ++i) { + if (poh_buffer[offset + i] != 0xff) + return false; + } + + read_pos_ += consumed; + parse_state_ = kParseBcmvHeader; + + return true; +} + +// Parses and validates a BCMV header. +bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) { + if (!header || parse_state_ != kParseBcmvHeader) + return false; + + PacketData bcmv_buffer; + std::size_t consumed = 0; + if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_], + kBcmvHeaderSize, &bcmv_buffer, + &consumed)) { + return false; + } + + std::size_t offset = 0; + header->id[0] = bcmv_buffer[offset++]; + header->id[1] = bcmv_buffer[offset++]; + header->id[2] = bcmv_buffer[offset++]; + header->id[3] = bcmv_buffer[offset++]; + + header->length = 0; + header->length |= bcmv_buffer[offset++] << 24; + header->length |= bcmv_buffer[offset++] << 16; + header->length |= bcmv_buffer[offset++] << 8; + header->length |= bcmv_buffer[offset++]; + + // Length stored in the BCMV header is followed by 2 bytes of 0 padding. + if (bcmv_buffer[offset++] != 0 || bcmv_buffer[offset++] != 0) + return false; + + if (!header->Valid()) + return false; + + parse_state_ = kFindStartCode; + read_pos_ += consumed; + + return true; +} + +bool VpxPesParser::FindStartCode(std::size_t origin, + std::size_t* offset) const { + if (read_pos_ + 2 >= pes_file_size_) + return false; + + const std::size_t length = pes_file_size_ - origin; + if (length < 3) + return false; + + const uint8_t* const data = &pes_file_data_[origin]; + for (std::size_t i = 0; i < length - 3; ++i) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + *offset = origin + i; + return true; + } + } + + return false; +} + +bool VpxPesParser::IsPayloadFragmented(const PesHeader& header) const { + return (header.packet_length != 0 && + (header.packet_length - kPesOptionalHeaderSize) != + header.bcmv_header.length); +} + +bool VpxPesParser::AccumulateFragmentedPayload(std::size_t pes_packet_length, + std::size_t payload_length) { + const std::size_t first_fragment_length = + pes_packet_length - kPesOptionalHeaderSize - kBcmvHeaderSize; + for (std::size_t i = 0; i < first_fragment_length; ++i) { + payload_.push_back(pes_file_data_[read_pos_ + i]); + } + read_pos_ += first_fragment_length; + parse_state_ = kFindStartCode; + + while (payload_.size() < payload_length) { + PesHeader header; + std::size_t packet_start_pos = read_pos_; + if (!FindStartCode(read_pos_, &packet_start_pos)) { + return false; + } + parse_state_ = kParsePesHeader; + read_pos_ = packet_start_pos; + + if (!ParsePesHeader(&header)) { + return false; + } + if (!ParsePesOptionalHeader(&header.opt_header)) { + return false; + } + + const std::size_t fragment_length = + header.packet_length - kPesOptionalHeaderSize; + std::size_t consumed = 0; + if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_], + fragment_length, &payload_, + &consumed)) { + return false; + } + read_pos_ += consumed; + } + return true; +} + +bool VpxPesParser::RemoveStartCodeEmulationPreventionBytes( + const std::uint8_t* raw_data, std::size_t bytes_required, + PacketData* processed_data, std::size_t* bytes_consumed) const { + if (bytes_required == 0 || !processed_data) + return false; + + std::size_t num_zeros = 0; + std::size_t bytes_copied = 0; + const std::uint8_t* const end_of_input = + &pes_file_data_[0] + pes_file_data_.size(); + std::size_t i; + for (i = 0; bytes_copied < bytes_required; ++i) { + if (raw_data + i > end_of_input) + return false; + + bool skip = false; + + const std::uint8_t byte = raw_data[i]; + if (byte == 0) { + ++num_zeros; + } else if (byte == 0x3 && num_zeros == 2) { + skip = true; + num_zeros = 0; + } else { + num_zeros = 0; + } + + if (skip == false) { + processed_data->push_back(byte); + ++bytes_copied; + } + } + *bytes_consumed = i; + return true; +} + +int VpxPesParser::BytesAvailable() const { + return static_cast<int>(pes_file_data_.size() - read_pos_); +} + +bool VpxPesParser::ParseNextPacket(PesHeader* header, VideoFrame* frame) { + if (!header || !frame || parse_state_ != kFindStartCode || + BytesAvailable() == 0) { + return false; + } + + std::size_t packet_start_pos = read_pos_; + if (!FindStartCode(read_pos_, &packet_start_pos)) { + return false; + } + parse_state_ = kParsePesHeader; + read_pos_ = packet_start_pos; + + if (!ParsePesHeader(header)) { + return false; + } + if (!ParsePesOptionalHeader(&header->opt_header)) { + return false; + } + if (!ParseBcmvHeader(&header->bcmv_header)) { + return false; + } + + // BCMV header length includes the length of the BCMVHeader itself. Adjust: + const std::size_t payload_length = + header->bcmv_header.length - BcmvHeader::size(); + + // Make sure there's enough input data to read the entire frame. + if (read_pos_ + payload_length > pes_file_data_.size()) { + // Need more data. + printf("VpxPesParser: Not enough data. Required: %u Available: %u\n", + static_cast<unsigned int>(payload_length), + static_cast<unsigned int>(pes_file_data_.size() - read_pos_)); + parse_state_ = kFindStartCode; + read_pos_ = packet_start_pos; + return false; + } + + if (IsPayloadFragmented(*header)) { + if (!AccumulateFragmentedPayload(header->packet_length, payload_length)) { + fprintf(stderr, "VpxPesParser: Failed parsing fragmented payload!\n"); + return false; + } + } else { + std::size_t consumed = 0; + if (!RemoveStartCodeEmulationPreventionBytes( + &pes_file_data_[read_pos_], payload_length, &payload_, &consumed)) { + return false; + } + read_pos_ += consumed; + } + + if (frame->buffer().capacity < payload_.size()) { + if (frame->Init(payload_.size()) == false) { + fprintf(stderr, "VpxPesParser: Out of memory.\n"); + return false; + } + } + frame->set_nanosecond_pts(Khz90TicksToNanoseconds(header->opt_header.pts)); + std::memcpy(frame->buffer().data.get(), &payload_[0], payload_.size()); + frame->SetBufferLength(payload_.size()); + + payload_.clear(); + parse_state_ = kFindStartCode; + + return true; +} + +} // namespace libwebm diff --git a/m2ts/vpxpes_parser.h b/m2ts/vpxpes_parser.h new file mode 100644 index 0000000..d18b4b7 --- /dev/null +++ b/m2ts/vpxpes_parser.h @@ -0,0 +1,177 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_M2TS_VPXPES_PARSER_H_ +#define LIBWEBM_M2TS_VPXPES_PARSER_H_ + +#include <cstdint> +#include <string> +#include <vector> + +#include "common/libwebm_util.h" +#include "common/video_frame.h" + +namespace libwebm { + +// Parser for VPx PES. Requires that the _entire_ PES stream can be stored in +// a std::vector<std::uint8_t> and read into memory when Open() is called. +// TODO(tomfinegan): Support incremental parse. +class VpxPesParser { + public: + typedef std::vector<std::uint8_t> PesFileData; + typedef std::vector<std::uint8_t> PacketData; + + enum ParseState { + kFindStartCode, + kParsePesHeader, + kParsePesOptionalHeader, + kParseBcmvHeader, + }; + + struct PesOptionalHeader { + int marker = 0; + int scrambling = 0; + int priority = 0; + int data_alignment = 0; + int copyright = 0; + int original = 0; + int has_pts = 0; + int has_dts = 0; + int unused_fields = 0; + int remaining_size = 0; + int pts_dts_flag = 0; + std::uint64_t pts = 0; + int stuffing_byte = 0; + }; + + struct BcmvHeader { + BcmvHeader() = default; + ~BcmvHeader() = default; + BcmvHeader(const BcmvHeader&) = delete; + BcmvHeader(BcmvHeader&&) = delete; + + // Convenience ctor for quick validation of expected values via operator== + // after parsing input. + explicit BcmvHeader(std::uint32_t len); + + bool operator==(const BcmvHeader& other) const; + + void Reset(); + bool Valid() const; + static std::size_t size() { return 10; } + + char id[4] = {0}; + std::uint32_t length = 0; + }; + + struct PesHeader { + std::uint8_t start_code[4] = {0}; + std::uint16_t packet_length = 0; + std::uint8_t stream_id = 0; + PesOptionalHeader opt_header; + BcmvHeader bcmv_header; + }; + + // Constants for validating known values from input data. + const std::uint8_t kMinVideoStreamId = 0xE0; + const std::uint8_t kMaxVideoStreamId = 0xEF; + const std::size_t kPesHeaderSize = 6; + const std::size_t kPesOptionalHeaderStartOffset = kPesHeaderSize; + const std::size_t kPesOptionalHeaderSize = 9; + const std::size_t kPesOptionalHeaderMarkerValue = 0x2; + const std::size_t kWebm2PesOptHeaderRemainingSize = 6; + const std::size_t kBcmvHeaderSize = 10; + + VpxPesParser() = default; + ~VpxPesParser() = default; + + // Opens file specified by |pes_file_path| and reads its contents. Returns + // true after successful read of input file. + bool Open(const std::string& pes_file_path); + + // Parses the next packet in the PES. PES header information is stored in + // |header|, and the frame payload is stored in |frame|. Returns true when + // a full frame has been consumed from the PES. + bool ParseNextPacket(PesHeader* header, VideoFrame* frame); + + // PES Header parsing utility functions. + // PES Header structure: + // Start code Stream ID Packet length (16 bits) + // / / ____/ + // | | / + // Byte0 Byte1 Byte2 Byte3 Byte4 Byte5 + // 0 0 1 X Y + bool VerifyPacketStartCode() const; + bool ReadStreamId(std::uint8_t* stream_id) const; + bool ReadPacketLength(std::uint16_t* packet_length) const; + + std::uint64_t pes_file_size() const { return pes_file_size_; } + const PesFileData& pes_file_data() const { return pes_file_data_; } + + // Returns number of unparsed bytes remaining. + int BytesAvailable() const; + + private: + // Parses and verifies the static 6 byte portion that begins every PES packet. + bool ParsePesHeader(PesHeader* header); + + // Parses a PES optional header, the optional header following the static + // header that begins the VPX PES packet. + // https://en.wikipedia.org/wiki/Packetized_elementary_stream + bool ParsePesOptionalHeader(PesOptionalHeader* header); + + // Parses and validates the BCMV header. This immediately follows the optional + // header. + bool ParseBcmvHeader(BcmvHeader* header); + + // Returns true when a start code is found and sets |offset| to the position + // of the start code relative to |pes_file_data_[read_pos_]|. + // Does not set |offset| value if the end of |pes_file_data_| is reached + // without locating a start code. + // Note: A start code is the byte sequence 0x00 0x00 0x01. + bool FindStartCode(std::size_t origin, std::size_t* offset) const; + + // Returns true when a PES packet containing a BCMV header contains only a + // portion of the frame payload length reported by the BCMV header. + bool IsPayloadFragmented(const PesHeader& header) const; + + // Parses PES and PES Optional header while accumulating payload data in + // |payload_|. + // Returns true once all payload fragments have been stored in |payload_|. + // Returns false if unable to accumulate full payload. + bool AccumulateFragmentedPayload(std::size_t pes_packet_length, + std::size_t payload_length); + + // The byte sequence 0x0 0x0 0x1 is a start code in PES. When PES muxers + // encounter 0x0 0x0 0x1 or 0x0 0x0 0x3, an additional 0x3 is inserted into + // the PES. The following change occurs: + // 0x0 0x0 0x1 => 0x0 0x0 0x3 0x1 + // 0x0 0x0 0x3 => 0x0 0x0 0x3 0x3 + // PES demuxers must reverse the change: + // 0x0 0x0 0x3 0x1 => 0x0 0x0 0x1 + // 0x0 0x0 0x3 0x3 => 0x0 0x0 0x3 + // PES optional header, BCMV header, and payload data must be preprocessed to + // avoid potentially invalid data due to the presence of inserted bytes. + // + // Removes start code emulation prevention bytes while copying data from + // |raw_data| to |processed_data|. Returns true when |bytes_required| bytes + // have been written to |processed_data|. Reports bytes consumed during the + // operation via |bytes_consumed|. + bool RemoveStartCodeEmulationPreventionBytes( + const std::uint8_t* raw_data, std::size_t bytes_required, + PacketData* processed_data, std::size_t* bytes_consumed) const; + + std::size_t pes_file_size_ = 0; + PacketData payload_; + PesFileData pes_file_data_; + std::size_t read_pos_ = 0; + ParseState parse_state_ = kFindStartCode; +}; + +} // namespace libwebm + +#endif // LIBWEBM_M2TS_VPXPES_PARSER_H_ diff --git a/m2ts/webm2pes.cc b/m2ts/webm2pes.cc new file mode 100644 index 0000000..fc4b314 --- /dev/null +++ b/m2ts/webm2pes.cc @@ -0,0 +1,551 @@ +// Copyright (c) 2015 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "m2ts/webm2pes.h" + +#include <algorithm> +#include <cassert> +#include <cstdio> +#include <cstring> +#include <new> +#include <vector> + +#include "common/libwebm_util.h" + +namespace libwebm { + +const std::size_t Webm2Pes::kMaxPayloadSize = 32768; + +namespace { + +std::string ToString(const char* str) { + return std::string((str == nullptr) ? "" : str); +} + +} // namespace + +// +// PesOptionalHeader methods. +// + +void PesOptionalHeader::SetPtsBits(std::int64_t pts_90khz) { + std::uint64_t* pts_bits = &pts.bits; + *pts_bits = 0; + + // PTS is broken up and stored in 40 bits as shown: + // + // PES PTS Only flag + // / Marker Marker Marker + // | / / / + // | | | | + // 7654 321 0 765432107654321 0 765432107654321 0 + // 0010 PTS 32-30 1 PTS 29-15 1 PTS 14-0 1 + const std::uint32_t pts1 = (pts_90khz >> 30) & 0x7; + const std::uint32_t pts2 = (pts_90khz >> 15) & 0x7FFF; + const std::uint32_t pts3 = pts_90khz & 0x7FFF; + + std::uint8_t buffer[5] = {0}; + // PTS only flag. + buffer[0] |= 1 << 5; + // Top 3 bits of PTS and 1 bit marker. + buffer[0] |= pts1 << 1; + // Marker. + buffer[0] |= 1; + + // Next 15 bits of pts and 1 bit marker. + // Top 8 bits of second PTS chunk. + buffer[1] |= (pts2 >> 7) & 0xff; + // bottom 7 bits of second PTS chunk. + buffer[2] |= (pts2 << 1); + // Marker. + buffer[2] |= 1; + + // Last 15 bits of pts and 1 bit marker. + // Top 8 bits of second PTS chunk. + buffer[3] |= (pts3 >> 7) & 0xff; + // bottom 7 bits of second PTS chunk. + buffer[4] |= (pts3 << 1); + // Marker. + buffer[4] |= 1; + + // Write bits into PesHeaderField. + std::memcpy(reinterpret_cast<std::uint8_t*>(pts_bits), buffer, 5); +} + +// Writes fields to |buffer| and returns true. Returns false when write or +// field value validation fails. +bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const { + if (buffer == nullptr) { + std::fprintf(stderr, "Webm2Pes: nullptr in opt header writer.\n"); + return false; + } + + const int kHeaderSize = 9; + std::uint8_t header[kHeaderSize] = {0}; + std::uint8_t* byte = header; + + if (marker.Check() != true || scrambling.Check() != true || + priority.Check() != true || data_alignment.Check() != true || + copyright.Check() != true || original.Check() != true || + has_pts.Check() != true || has_dts.Check() != true || + pts.Check() != true || stuffing_byte.Check() != true) { + std::fprintf(stderr, "Webm2Pes: Invalid PES Optional Header field.\n"); + return false; + } + + // TODO(tomfinegan): As noted in above, the PesHeaderFields should be an + // array (or some data structure) that can be iterated over. + + // First byte of header, fields: marker, scrambling, priority, alignment, + // copyright, original. + *byte = 0; + *byte |= marker.bits << marker.shift; + *byte |= scrambling.bits << scrambling.shift; + *byte |= priority.bits << priority.shift; + *byte |= data_alignment.bits << data_alignment.shift; + *byte |= copyright.bits << copyright.shift; + *byte |= original.bits << original.shift; + + // Second byte of header, fields: has_pts, has_dts, unused fields. + *++byte = 0; + if (write_pts == true) + *byte |= has_pts.bits << has_pts.shift; + + *byte |= has_dts.bits << has_dts.shift; + + // Third byte of header, fields: remaining size of header. + *++byte = remaining_size.bits & 0xff; // Field is 8 bits wide. + + int num_stuffing_bytes = + (pts.num_bits + 7) / 8 + 1 /* always 1 stuffing byte */; + if (write_pts == true) { + // Write the PTS value as big endian and adjust stuffing byte count + // accordingly. + *++byte = pts.bits & 0xff; + *++byte = (pts.bits >> 8) & 0xff; + *++byte = (pts.bits >> 16) & 0xff; + *++byte = (pts.bits >> 24) & 0xff; + *++byte = (pts.bits >> 32) & 0xff; + num_stuffing_bytes = 1; + } + + // Add the stuffing byte(s). + for (int i = 0; i < num_stuffing_bytes; ++i) + *++byte = stuffing_byte.bits & 0xff; + + return CopyAndEscapeStartCodes(&header[0], kHeaderSize, buffer); +} + +// +// BCMVHeader methods. +// + +bool BCMVHeader::Write(PacketDataBuffer* buffer) const { + if (buffer == nullptr) { + std::fprintf(stderr, "Webm2Pes: nullptr for buffer in BCMV Write.\n"); + return false; + } + const int kBcmvSize = 4; + for (int i = 0; i < kBcmvSize; ++i) + buffer->push_back(bcmv[i]); + + // Note: The 4 byte length field must include the size of the BCMV header. + const int kRemainingBytes = 6; + const uint32_t bcmv_total_length = length + static_cast<uint32_t>(size()); + const uint8_t bcmv_buffer[kRemainingBytes] = { + static_cast<std::uint8_t>((bcmv_total_length >> 24) & 0xff), + static_cast<std::uint8_t>((bcmv_total_length >> 16) & 0xff), + static_cast<std::uint8_t>((bcmv_total_length >> 8) & 0xff), + static_cast<std::uint8_t>(bcmv_total_length & 0xff), + 0, + 0 /* 2 bytes 0 padding */}; + + return CopyAndEscapeStartCodes(bcmv_buffer, kRemainingBytes, buffer); +} + +// +// PesHeader methods. +// + +// Writes out the header to |buffer|. Calls PesOptionalHeader::Write() to write +// |optional_header| contents. Returns true when successful, false otherwise. +bool PesHeader::Write(bool write_pts, PacketDataBuffer* buffer) const { + if (buffer == nullptr) { + std::fprintf(stderr, "Webm2Pes: nullptr in header writer.\n"); + return false; + } + + // Write |start_code|. + const int kStartCodeLength = 4; + for (int i = 0; i < kStartCodeLength; ++i) + buffer->push_back(start_code[i]); + + // The length field here reports number of bytes following the field. The + // length of the optional header must be added to the payload length set by + // the user. + const std::size_t header_length = + packet_length + optional_header.size_in_bytes(); + if (header_length > UINT16_MAX) + return false; + + // Write |header_length| as big endian. + std::uint8_t byte = (header_length >> 8) & 0xff; + buffer->push_back(byte); + byte = header_length & 0xff; + buffer->push_back(byte); + + // Write the (not really) optional header. + if (optional_header.Write(write_pts, buffer) != true) { + std::fprintf(stderr, "Webm2Pes: PES optional header write failed."); + return false; + } + return true; +} + +// +// Webm2Pes methods. +// + +bool Webm2Pes::ConvertToFile() { + if (input_file_name_.empty() || output_file_name_.empty()) { + std::fprintf(stderr, "Webm2Pes: input and/or output file name(s) empty.\n"); + return false; + } + + output_file_ = FilePtr(fopen(output_file_name_.c_str(), "wb"), FILEDeleter()); + if (output_file_ == nullptr) { + std::fprintf(stderr, "Webm2Pes: Cannot open %s for output.\n", + output_file_name_.c_str()); + return false; + } + + if (InitWebmParser() != true) { + std::fprintf(stderr, "Webm2Pes: Cannot initialize WebM parser.\n"); + return false; + } + + // Walk clusters in segment. + const mkvparser::Cluster* cluster = webm_parser_->GetFirst(); + while (cluster != nullptr && cluster->EOS() == false) { + const mkvparser::BlockEntry* block_entry = nullptr; + std::int64_t block_status = cluster->GetFirst(block_entry); + if (block_status < 0) { + std::fprintf(stderr, "Webm2Pes: Cannot parse first block in %s.\n", + input_file_name_.c_str()); + return false; + } + + // Walk blocks in cluster. + while (block_entry != nullptr && block_entry->EOS() == false) { + const mkvparser::Block* block = block_entry->GetBlock(); + if (block->GetTrackNumber() == video_track_num_) { + const int frame_count = block->GetFrameCount(); + + // Walk frames in block. + for (int frame_num = 0; frame_num < frame_count; ++frame_num) { + const mkvparser::Block::Frame& mkvparser_frame = + block->GetFrame(frame_num); + + // Read the frame. + VideoFrame vpx_frame(block->GetTime(cluster), codec_); + if (ReadVideoFrame(mkvparser_frame, &vpx_frame) == false) { + fprintf(stderr, "Webm2Pes: frame read failed.\n"); + return false; + } + + // Write frame out as PES packet(s). + if (WritePesPacket(vpx_frame, &packet_data_) == false) { + std::fprintf(stderr, "Webm2Pes: WritePesPacket failed.\n"); + return false; + } + + // Write contents of |packet_data_| to |output_file_|. + if (std::fwrite(&packet_data_[0], 1, packet_data_.size(), + output_file_.get()) != packet_data_.size()) { + std::fprintf(stderr, "Webm2Pes: packet payload write failed.\n"); + return false; + } + bytes_written_ += packet_data_.size(); + } + } + block_status = cluster->GetNext(block_entry, block_entry); + if (block_status < 0) { + std::fprintf(stderr, "Webm2Pes: Cannot parse block in %s.\n", + input_file_name_.c_str()); + return false; + } + } + + cluster = webm_parser_->GetNext(cluster); + } + + std::fflush(output_file_.get()); + return true; +} + +bool Webm2Pes::ConvertToPacketReceiver() { + if (input_file_name_.empty() || packet_sink_ == nullptr) { + std::fprintf(stderr, "Webm2Pes: input file name empty or null sink.\n"); + return false; + } + + if (InitWebmParser() != true) { + std::fprintf(stderr, "Webm2Pes: Cannot initialize WebM parser.\n"); + return false; + } + + // Walk clusters in segment. + const mkvparser::Cluster* cluster = webm_parser_->GetFirst(); + while (cluster != nullptr && cluster->EOS() == false) { + const mkvparser::BlockEntry* block_entry = nullptr; + std::int64_t block_status = cluster->GetFirst(block_entry); + if (block_status < 0) { + std::fprintf(stderr, "Webm2Pes: Cannot parse first block in %s.\n", + input_file_name_.c_str()); + return false; + } + + // Walk blocks in cluster. + while (block_entry != nullptr && block_entry->EOS() == false) { + const mkvparser::Block* block = block_entry->GetBlock(); + if (block->GetTrackNumber() == video_track_num_) { + const int frame_count = block->GetFrameCount(); + + // Walk frames in block. + for (int frame_num = 0; frame_num < frame_count; ++frame_num) { + const mkvparser::Block::Frame& mkvparser_frame = + block->GetFrame(frame_num); + + // Read the frame. + VideoFrame frame(block->GetTime(cluster), codec_); + if (ReadVideoFrame(mkvparser_frame, &frame) == false) { + fprintf(stderr, "Webm2Pes: frame read failed.\n"); + return false; + } + + // Write frame out as PES packet(s). + if (WritePesPacket(frame, &packet_data_) == false) { + std::fprintf(stderr, "Webm2Pes: WritePesPacket failed.\n"); + return false; + } + if (packet_sink_->ReceivePacket(packet_data_) != true) { + std::fprintf(stderr, "Webm2Pes: ReceivePacket failed.\n"); + return false; + } + bytes_written_ += packet_data_.size(); + } + } + block_status = cluster->GetNext(block_entry, block_entry); + if (block_status < 0) { + std::fprintf(stderr, "Webm2Pes: Cannot parse block in %s.\n", + input_file_name_.c_str()); + return false; + } + } + + cluster = webm_parser_->GetNext(cluster); + } + + return true; +} + +bool Webm2Pes::InitWebmParser() { + if (webm_reader_.Open(input_file_name_.c_str()) != 0) { + std::fprintf(stderr, "Webm2Pes: Cannot open %s as input.\n", + input_file_name_.c_str()); + return false; + } + + using mkvparser::Segment; + Segment* webm_parser = nullptr; + if (Segment::CreateInstance(&webm_reader_, 0 /* pos */, + webm_parser /* Segment*& */) != 0) { + std::fprintf(stderr, "Webm2Pes: Cannot create WebM parser.\n"); + return false; + } + webm_parser_.reset(webm_parser); + + if (webm_parser_->Load() != 0) { + std::fprintf(stderr, "Webm2Pes: Cannot parse %s.\n", + input_file_name_.c_str()); + return false; + } + + // Make sure there's a video track. + const mkvparser::Tracks* tracks = webm_parser_->GetTracks(); + if (tracks == nullptr) { + std::fprintf(stderr, "Webm2Pes: %s has no tracks.\n", + input_file_name_.c_str()); + return false; + } + + timecode_scale_ = webm_parser_->GetInfo()->GetTimeCodeScale(); + + for (int track_index = 0; + track_index < static_cast<int>(tracks->GetTracksCount()); + ++track_index) { + const mkvparser::Track* track = tracks->GetTrackByIndex(track_index); + if (track && track->GetType() == mkvparser::Track::kVideo) { + const std::string codec_id = ToString(track->GetCodecId()); + if (codec_id == std::string("V_VP8")) { + codec_ = VideoFrame::kVP8; + } else if (codec_id == std::string("V_VP9")) { + codec_ = VideoFrame::kVP9; + } else { + fprintf(stderr, "Webm2Pes: Codec must be VP8 or VP9.\n"); + return false; + } + video_track_num_ = track_index + 1; + break; + } + } + if (video_track_num_ < 1) { + std::fprintf(stderr, "Webm2Pes: No video track found in %s.\n", + input_file_name_.c_str()); + return false; + } + return true; +} + +bool Webm2Pes::ReadVideoFrame(const mkvparser::Block::Frame& mkvparser_frame, + VideoFrame* frame) { + if (mkvparser_frame.len < 1 || frame == nullptr) + return false; + + const std::size_t mkv_len = static_cast<std::size_t>(mkvparser_frame.len); + if (mkv_len > frame->buffer().capacity) { + const std::size_t new_size = 2 * mkv_len; + if (frame->Init(new_size) == false) { + std::fprintf(stderr, "Webm2Pes: Out of memory.\n"); + return false; + } + } + if (mkvparser_frame.Read(&webm_reader_, frame->buffer().data.get()) != 0) { + std::fprintf(stderr, "Webm2Pes: Error reading VPx frame!\n"); + return false; + } + return frame->SetBufferLength(mkv_len); +} + +bool Webm2Pes::WritePesPacket(const VideoFrame& frame, + PacketDataBuffer* packet_data) { + if (frame.buffer().data.get() == nullptr || frame.buffer().length < 1) + return false; + + Ranges frame_ranges; + if (frame.codec() == VideoFrame::kVP9) { + bool error = false; + const bool has_superframe_index = + ParseVP9SuperFrameIndex(frame.buffer().data.get(), + frame.buffer().length, &frame_ranges, &error); + if (error) { + std::fprintf(stderr, "Webm2Pes: Superframe index parse failed.\n"); + return false; + } + if (has_superframe_index == false) { + frame_ranges.push_back(Range(0, frame.buffer().length)); + } + } else { + frame_ranges.push_back(Range(0, frame.buffer().length)); + } + + const std::int64_t khz90_pts = + NanosecondsTo90KhzTicks(frame.nanosecond_pts()); + PesHeader header; + header.optional_header.SetPtsBits(khz90_pts); + + packet_data->clear(); + + for (const Range& packet_payload_range : frame_ranges) { + std::size_t extra_bytes = 0; + if (packet_payload_range.length > kMaxPayloadSize) { + extra_bytes = packet_payload_range.length - kMaxPayloadSize; + } + if (packet_payload_range.length + packet_payload_range.offset > + frame.buffer().length) { + std::fprintf(stderr, "Webm2Pes: Invalid frame length.\n"); + return false; + } + + // First packet of new frame. Always include PTS and BCMV header. + header.packet_length = + packet_payload_range.length - extra_bytes + BCMVHeader::size(); + if (header.Write(true, packet_data) != true) { + std::fprintf(stderr, "Webm2Pes: packet header write failed.\n"); + return false; + } + + BCMVHeader bcmv_header(static_cast<uint32_t>(packet_payload_range.length)); + if (bcmv_header.Write(packet_data) != true) { + std::fprintf(stderr, "Webm2Pes: BCMV write failed.\n"); + return false; + } + + // Insert the payload at the end of |packet_data|. + const std::uint8_t* const payload_start = + frame.buffer().data.get() + packet_payload_range.offset; + + const std::size_t bytes_to_copy = packet_payload_range.length - extra_bytes; + if (CopyAndEscapeStartCodes(payload_start, bytes_to_copy, packet_data) == + false) { + fprintf(stderr, "Webm2Pes: Payload write failed.\n"); + return false; + } + + std::size_t bytes_copied = bytes_to_copy; + while (extra_bytes) { + // Write PES packets for the remaining data, but omit the PTS and BCMV + // header. + const std::size_t extra_bytes_to_copy = + std::min(kMaxPayloadSize, extra_bytes); + extra_bytes -= extra_bytes_to_copy; + header.packet_length = extra_bytes_to_copy; + if (header.Write(false, packet_data) != true) { + fprintf(stderr, "Webm2pes: fragment write failed.\n"); + return false; + } + + const std::uint8_t* fragment_start = payload_start + bytes_copied; + if (CopyAndEscapeStartCodes(fragment_start, extra_bytes_to_copy, + packet_data) == false) { + fprintf(stderr, "Webm2Pes: Payload write failed.\n"); + return false; + } + + bytes_copied += extra_bytes_to_copy; + } + } + + return true; +} + +bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input, + std::size_t raw_input_length, + PacketDataBuffer* packet_buffer) { + if (raw_input == nullptr || raw_input_length < 1 || packet_buffer == nullptr) + return false; + + int num_zeros = 0; + for (std::size_t i = 0; i < raw_input_length; ++i) { + const uint8_t byte = raw_input[i]; + + if (byte == 0) { + ++num_zeros; + } else if (num_zeros >= 2 && (byte == 0x1 || byte == 0x3)) { + packet_buffer->push_back(0x3); + num_zeros = 0; + } else { + num_zeros = 0; + } + + packet_buffer->push_back(byte); + } + + return true; +} + +} // namespace libwebm diff --git a/m2ts/webm2pes.h b/m2ts/webm2pes.h new file mode 100644 index 0000000..6dcb0fd --- /dev/null +++ b/m2ts/webm2pes.h @@ -0,0 +1,274 @@ +// Copyright (c) 2015 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_M2TS_WEBM2PES_H_ +#define LIBWEBM_M2TS_WEBM2PES_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#include "common/libwebm_util.h" +#include "common/video_frame.h" +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" + +// Webm2pes +// +// Webm2pes consumes a WebM file containing a VP8 or VP9 video stream and +// outputs a PES stream suitable for inclusion in a MPEG2 Transport Stream. +// +// In the simplest case the PES stream output by Webm2pes consists of a sequence +// of PES packets with the following structure: +// | PES Header w/PTS | BCMV Header | Payload (VPx frame) | +// +// More typically the output will look like the following due to the PES +// payload size limitations caused by the format of the PES header. +// The PES header contains only 2 bytes of storage for expressing payload size. +// VPx PES streams containing fragmented packets look like this: +// +// | PH PTS | BCMV | Payload fragment 1 | PH | Payload fragment 2 | ... +// +// PH = PES Header +// PH PTS = PES Header with PTS +// BCMV = BCMV Header +// +// Note that start codes are properly escaped by Webm2pes, and start code +// emulation prevention bytes must be stripped from the output stream before +// it can be parsed. + +namespace libwebm { + +// Stores a value and its size in bits for writing into a PES Optional Header. +// Maximum size is 64 bits. Users may call the Check() method to perform minimal +// validation (size > 0 and <= 64). +struct PesHeaderField { + PesHeaderField(std::uint64_t value, std::uint32_t size_in_bits, + std::uint8_t byte_index, std::uint8_t bits_to_shift) + : bits(value), + num_bits(size_in_bits), + index(byte_index), + shift(bits_to_shift) {} + PesHeaderField() = delete; + PesHeaderField(const PesHeaderField&) = default; + PesHeaderField(PesHeaderField&&) = default; + ~PesHeaderField() = default; + bool Check() const { + return num_bits > 0 && num_bits <= 64 && shift >= 0 && shift < 64; + } + + // Value to be stored in the field. + std::uint64_t bits; + + // Number of bits in the value. + const int num_bits; + + // Index into the header for the byte in which |bits| will be written. + const std::uint8_t index; + + // Number of bits to shift value before or'ing. + const int shift; +}; + +// Data is stored in buffers before being written to output files. +typedef std::vector<std::uint8_t> PacketDataBuffer; + +// Storage for PES Optional Header values. Fields written in order using sizes +// specified. +struct PesOptionalHeader { + // TODO(tomfinegan): The fields could be in an array, which would allow the + // code writing the optional header to iterate over the fields instead of + // having code for dealing with each one. + + // 2 bits (marker): 2 ('10') + const PesHeaderField marker = PesHeaderField(2, 2, 0, 6); + + // 2 bits (no scrambling): 0x0 ('00') + const PesHeaderField scrambling = PesHeaderField(0, 2, 0, 4); + + // 1 bit (priority): 0x0 ('0') + const PesHeaderField priority = PesHeaderField(0, 1, 0, 3); + + // TODO(tomfinegan): The BCMV header could be considered a sync word, and this + // field should be 1 when a sync word follows the packet. Clarify. + // 1 bit (data alignment): 0x0 ('0') + const PesHeaderField data_alignment = PesHeaderField(0, 1, 0, 2); + + // 1 bit (copyright): 0x0 ('0') + const PesHeaderField copyright = PesHeaderField(0, 1, 0, 1); + + // 1 bit (original/copy): 0x0 ('0') + const PesHeaderField original = PesHeaderField(0, 1, 0, 0); + + // 1 bit (has_pts): 0x1 ('1') + const PesHeaderField has_pts = PesHeaderField(1, 1, 1, 7); + + // 1 bit (has_dts): 0x0 ('0') + const PesHeaderField has_dts = PesHeaderField(0, 1, 1, 6); + + // 6 bits (unused fields): 0x0 ('000000') + const PesHeaderField unused = PesHeaderField(0, 6, 1, 0); + + // 8 bits (size of remaining data in the Header). + const PesHeaderField remaining_size = PesHeaderField(6, 8, 2, 0); + + // PTS: 5 bytes + // 4 bits (flag: PTS present, but no DTS): 0x2 ('0010') + // 36 bits (90khz PTS): + // top 3 bits + // marker ('1') + // middle 15 bits + // marker ('1') + // bottom 15 bits + // marker ('1') + PesHeaderField pts = PesHeaderField(0, 40, 3, 0); + + PesHeaderField stuffing_byte = PesHeaderField(0xFF, 8, 8, 0); + + // PTS omitted in fragments. Size remains unchanged: More stuffing bytes. + bool fragment = false; + + static std::size_t size_in_bytes() { return 9; } + + // Writes |pts_90khz| to |pts| per format described at its declaration above. + void SetPtsBits(std::int64_t pts_90khz); + + // Writes fields to |buffer| and returns true. Returns false when write or + // field value validation fails. + bool Write(bool write_pts, PacketDataBuffer* buffer) const; +}; + +// Describes custom 10 byte header that immediately follows the PES Optional +// Header in each PES packet output by Webm2Pes: +// 4 byte 'B' 'C' 'M' 'V' +// 4 byte big-endian length of frame +// 2 bytes 0 padding +struct BCMVHeader { + explicit BCMVHeader(std::uint32_t frame_length) : length(frame_length) {} + BCMVHeader() = delete; + BCMVHeader(const BCMVHeader&) = delete; + BCMVHeader(BCMVHeader&&) = delete; + ~BCMVHeader() = default; + const std::uint8_t bcmv[4] = {'B', 'C', 'M', 'V'}; + const std::uint32_t length; + + static std::size_t size() { return 10; } + + // Write the BCMV Header into |buffer|. Caller responsible for ensuring + // destination buffer is of size >= BCMVHeader::size(). + bool Write(PacketDataBuffer* buffer) const; + bool Write(uint8_t* buffer); +}; + +struct PesHeader { + const std::uint8_t start_code[4] = { + 0x00, 0x00, + 0x01, // 0x000001 is the PES packet start code prefix. + 0xE0}; // 0xE0 is the minimum video stream ID. + std::uint16_t packet_length = 0; // Number of bytes _after_ this field. + PesOptionalHeader optional_header; + std::size_t size() const { + return optional_header.size_in_bytes() + + 6 /* start_code + packet_length */ + packet_length; + } + + // Writes out the header to |buffer|. Calls PesOptionalHeader::Write() to + // write |optional_header| contents. Returns true when successful, false + // otherwise. + bool Write(bool write_pts, PacketDataBuffer* buffer) const; +}; + +class PacketReceiverInterface { + public: + virtual ~PacketReceiverInterface() {} + virtual bool ReceivePacket(const PacketDataBuffer& packet) = 0; +}; + +// Converts the VP9 track of a WebM file to a Packetized Elementary Stream +// suitable for use in a MPEG2TS. +// https://en.wikipedia.org/wiki/Packetized_elementary_stream +// https://en.wikipedia.org/wiki/MPEG_transport_stream +class Webm2Pes { + public: + static const std::size_t kMaxPayloadSize; + + Webm2Pes(const std::string& input_file, const std::string& output_file) + : input_file_name_(input_file), output_file_name_(output_file) {} + Webm2Pes(const std::string& input_file, PacketReceiverInterface* packet_sink) + : input_file_name_(input_file), packet_sink_(packet_sink) {} + + Webm2Pes() = delete; + Webm2Pes(const Webm2Pes&) = delete; + Webm2Pes(Webm2Pes&&) = delete; + ~Webm2Pes() = default; + + // Converts the VPx video stream to a PES file and returns true. Returns false + // to report failure. + bool ConvertToFile(); + + // Converts the VPx video stream to a sequence of PES packets, and calls the + // PacketReceiverInterface::ReceivePacket() once for each VPx frame. The + // packet sent to the receiver may contain multiple PES packets. Returns only + // after full conversion or error. Returns true for success, and false when + // an error occurs. + bool ConvertToPacketReceiver(); + + // Writes |vpx_frame| out as PES packet[s] and stores output in |packet_data|. + // Returns true for success, false for failure. + static bool WritePesPacket(const VideoFrame& frame, + PacketDataBuffer* packet_data); + + uint64_t bytes_written() const { return bytes_written_; } + + private: + bool InitWebmParser(); + bool ReadVideoFrame(const mkvparser::Block::Frame& mkvparser_frame, + VideoFrame* frame); + + const std::string input_file_name_; + const std::string output_file_name_; + std::unique_ptr<mkvparser::Segment> webm_parser_; + mkvparser::MkvReader webm_reader_; + FilePtr output_file_; + + // Video track num in the WebM file. + int video_track_num_ = 0; + + // Video codec reported by CodecName from Video TrackEntry. + VideoFrame::Codec codec_; + + // Input timecode scale. + std::int64_t timecode_scale_ = 1000000; + + // Packet sink; when constructed with a PacketReceiverInterface*, packet and + // type of packet are sent to |packet_sink_| instead of written to an output + // file. + PacketReceiverInterface* packet_sink_ = nullptr; + + PacketDataBuffer packet_data_; + + std::uint64_t bytes_written_ = 0; +}; + +// Copies |raw_input_length| bytes from |raw_input| to |packet_buffer| while +// escaping start codes. Returns true when bytes are successfully copied. +// A start code is the 3 byte sequence 0x00 0x00 0x01. When +// the sequence is encountered, the value 0x03 is inserted. To avoid +// any ambiguity at reassembly time, the same is done for the sequence +// 0x00 0x00 0x03. So, the following transformation occurs for when either +// of the noted sequences is encountered: +// +// 0x00 0x00 0x01 => 0x00 0x00 0x03 0x01 +// 0x00 0x00 0x03 => 0x00 0x00 0x03 0x03 +bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input, + std::size_t raw_input_length, + PacketDataBuffer* packet_buffer); +} // namespace libwebm + +#endif // LIBWEBM_M2TS_WEBM2PES_H_ diff --git a/m2ts/webm2pes_main.cc b/m2ts/webm2pes_main.cc new file mode 100644 index 0000000..075e55c --- /dev/null +++ b/m2ts/webm2pes_main.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2015 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "m2ts/webm2pes.h" + +#include <cstdio> +#include <cstdlib> +#include <string> + +namespace { + +void Usage(const char* argv[]) { + printf("Usage: %s <WebM file> <output file>", argv[0]); +} + +} // namespace + +int main(int argc, const char* argv[]) { + if (argc < 3) { + Usage(argv); + return EXIT_FAILURE; + } + + const std::string input_path = argv[1]; + const std::string output_path = argv[2]; + + libwebm::Webm2Pes converter(input_path, output_path); + return converter.ConvertToFile() == true ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/mkvmuxer.hpp b/mkvmuxer.hpp new file mode 100644 index 0000000..092592b --- /dev/null +++ b/mkvmuxer.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_MKVMUXER_HPP_ +#define LIBWEBM_MKVMUXER_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "mkvmuxer/mkvmuxer.h" + +#endif // LIBWEBM_MKVMUXER_HPP_ diff --git a/mkvmuxer/mkvmuxer.cc b/mkvmuxer/mkvmuxer.cc new file mode 100644 index 0000000..5120312 --- /dev/null +++ b/mkvmuxer/mkvmuxer.cc @@ -0,0 +1,4221 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "mkvmuxer/mkvmuxer.h" + +#include <stdint.h> + +#include <cfloat> +#include <climits> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <memory> +#include <new> +#include <string> +#include <vector> + +#include "common/webmids.h" +#include "mkvmuxer/mkvmuxerutil.h" +#include "mkvmuxer/mkvwriter.h" +#include "mkvparser/mkvparser.h" + +namespace mkvmuxer { + +const float PrimaryChromaticity::kChromaticityMin = 0.0f; +const float PrimaryChromaticity::kChromaticityMax = 1.0f; +const float MasteringMetadata::kMinLuminance = 0.0f; +const float MasteringMetadata::kMinLuminanceMax = 999.99f; +const float MasteringMetadata::kMaxLuminanceMax = 9999.99f; +const float MasteringMetadata::kValueNotPresent = FLT_MAX; +const uint64_t Colour::kValueNotPresent = UINT64_MAX; + +namespace { + +const char kDocTypeWebm[] = "webm"; +const char kDocTypeMatroska[] = "matroska"; + +// Deallocate the string designated by |dst|, and then copy the |src| +// string to |dst|. The caller owns both the |src| string and the +// |dst| copy (hence the caller is responsible for eventually +// deallocating the strings, either directly, or indirectly via +// StrCpy). Returns true if the source string was successfully copied +// to the destination. +bool StrCpy(const char* src, char** dst_ptr) { + if (dst_ptr == NULL) + return false; + + char*& dst = *dst_ptr; + + delete[] dst; + dst = NULL; + + if (src == NULL) + return true; + + const size_t size = strlen(src) + 1; + + dst = new (std::nothrow) char[size]; // NOLINT + if (dst == NULL) + return false; + + strcpy(dst, src); // NOLINT + return true; +} + +typedef std::unique_ptr<PrimaryChromaticity> PrimaryChromaticityPtr; +bool CopyChromaticity(const PrimaryChromaticity* src, + PrimaryChromaticityPtr* dst) { + if (!dst) + return false; + + dst->reset(new (std::nothrow) PrimaryChromaticity(src->x(), src->y())); + if (!dst->get()) + return false; + + return true; +} + +} // namespace + +/////////////////////////////////////////////////////////////// +// +// IMkvWriter Class + +IMkvWriter::IMkvWriter() {} + +IMkvWriter::~IMkvWriter() {} + +bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version, + const char* const doc_type) { + // Level 0 + uint64_t size = + EbmlElementSize(libwebm::kMkvEBMLVersion, static_cast<uint64>(1)); + size += EbmlElementSize(libwebm::kMkvEBMLReadVersion, static_cast<uint64>(1)); + size += EbmlElementSize(libwebm::kMkvEBMLMaxIDLength, static_cast<uint64>(4)); + size += + EbmlElementSize(libwebm::kMkvEBMLMaxSizeLength, static_cast<uint64>(8)); + size += EbmlElementSize(libwebm::kMkvDocType, doc_type); + size += EbmlElementSize(libwebm::kMkvDocTypeVersion, + static_cast<uint64>(doc_type_version)); + size += + EbmlElementSize(libwebm::kMkvDocTypeReadVersion, static_cast<uint64>(2)); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvEBML, size)) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvEBMLVersion, + static_cast<uint64>(1))) { + return false; + } + if (!WriteEbmlElement(writer, libwebm::kMkvEBMLReadVersion, + static_cast<uint64>(1))) { + return false; + } + if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxIDLength, + static_cast<uint64>(4))) { + return false; + } + if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxSizeLength, + static_cast<uint64>(8))) { + return false; + } + if (!WriteEbmlElement(writer, libwebm::kMkvDocType, doc_type)) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeVersion, + static_cast<uint64>(doc_type_version))) { + return false; + } + if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeReadVersion, + static_cast<uint64>(2))) { + return false; + } + + return true; +} + +bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version) { + return WriteEbmlHeader(writer, doc_type_version, kDocTypeWebm); +} + +bool WriteEbmlHeader(IMkvWriter* writer) { + return WriteEbmlHeader(writer, mkvmuxer::Segment::kDefaultDocTypeVersion); +} + +bool ChunkedCopy(mkvparser::IMkvReader* source, mkvmuxer::IMkvWriter* dst, + int64_t start, int64_t size) { + // TODO(vigneshv): Check if this is a reasonable value. + const uint32_t kBufSize = 2048; + uint8_t* buf = new uint8_t[kBufSize]; + int64_t offset = start; + while (size > 0) { + const int64_t read_len = (size > kBufSize) ? kBufSize : size; + if (source->Read(offset, static_cast<long>(read_len), buf)) + return false; + dst->Write(buf, static_cast<uint32_t>(read_len)); + offset += read_len; + size -= read_len; + } + delete[] buf; + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Frame Class + +Frame::Frame() + : add_id_(0), + additional_(NULL), + additional_length_(0), + duration_(0), + duration_set_(false), + frame_(NULL), + is_key_(false), + length_(0), + track_number_(0), + timestamp_(0), + discard_padding_(0), + reference_block_timestamp_(0), + reference_block_timestamp_set_(false) {} + +Frame::~Frame() { + delete[] frame_; + delete[] additional_; +} + +bool Frame::CopyFrom(const Frame& frame) { + delete[] frame_; + frame_ = NULL; + length_ = 0; + if (frame.length() > 0 && frame.frame() != NULL && + !Init(frame.frame(), frame.length())) { + return false; + } + add_id_ = 0; + delete[] additional_; + additional_ = NULL; + additional_length_ = 0; + if (frame.additional_length() > 0 && frame.additional() != NULL && + !AddAdditionalData(frame.additional(), frame.additional_length(), + frame.add_id())) { + return false; + } + duration_ = frame.duration(); + duration_set_ = frame.duration_set(); + is_key_ = frame.is_key(); + track_number_ = frame.track_number(); + timestamp_ = frame.timestamp(); + discard_padding_ = frame.discard_padding(); + reference_block_timestamp_ = frame.reference_block_timestamp(); + reference_block_timestamp_set_ = frame.reference_block_timestamp_set(); + return true; +} + +bool Frame::Init(const uint8_t* frame, uint64_t length) { + uint8_t* const data = + new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT + if (!data) + return false; + + delete[] frame_; + frame_ = data; + length_ = length; + + memcpy(frame_, frame, static_cast<size_t>(length_)); + return true; +} + +bool Frame::AddAdditionalData(const uint8_t* additional, uint64_t length, + uint64_t add_id) { + uint8_t* const data = + new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT + if (!data) + return false; + + delete[] additional_; + additional_ = data; + additional_length_ = length; + add_id_ = add_id; + + memcpy(additional_, additional, static_cast<size_t>(additional_length_)); + return true; +} + +bool Frame::IsValid() const { + if (length_ == 0 || !frame_) { + return false; + } + if ((additional_length_ != 0 && !additional_) || + (additional_ != NULL && additional_length_ == 0)) { + return false; + } + if (track_number_ == 0 || track_number_ > kMaxTrackNumber) { + return false; + } + if (!CanBeSimpleBlock() && !is_key_ && !reference_block_timestamp_set_) { + return false; + } + return true; +} + +bool Frame::CanBeSimpleBlock() const { + return additional_ == NULL && discard_padding_ == 0 && duration_ == 0; +} + +void Frame::set_duration(uint64_t duration) { + duration_ = duration; + duration_set_ = true; +} + +void Frame::set_reference_block_timestamp(int64_t reference_block_timestamp) { + reference_block_timestamp_ = reference_block_timestamp; + reference_block_timestamp_set_ = true; +} + +/////////////////////////////////////////////////////////////// +// +// CuePoint Class + +CuePoint::CuePoint() + : time_(0), + track_(0), + cluster_pos_(0), + block_number_(1), + output_block_number_(true) {} + +CuePoint::~CuePoint() {} + +bool CuePoint::Write(IMkvWriter* writer) const { + if (!writer || track_ < 1 || cluster_pos_ < 1) + return false; + + uint64_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition, + static_cast<uint64>(cluster_pos_)); + size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_)); + if (output_block_number_ && block_number_ > 1) + size += EbmlElementSize(libwebm::kMkvCueBlockNumber, + static_cast<uint64>(block_number_)); + const uint64_t track_pos_size = + EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size; + const uint64_t payload_size = + EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) + + track_pos_size; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvCuePoint, payload_size)) + return false; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvCueTime, + static_cast<uint64>(time_))) { + return false; + } + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvCueTrackPositions, size)) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvCueTrack, + static_cast<uint64>(track_))) { + return false; + } + if (!WriteEbmlElement(writer, libwebm::kMkvCueClusterPosition, + static_cast<uint64>(cluster_pos_))) { + return false; + } + if (output_block_number_ && block_number_ > 1) { + if (!WriteEbmlElement(writer, libwebm::kMkvCueBlockNumber, + static_cast<uint64>(block_number_))) { + return false; + } + } + + const int64_t stop_position = writer->Position(); + if (stop_position < 0) + return false; + + if (stop_position - payload_position != static_cast<int64_t>(payload_size)) + return false; + + return true; +} + +uint64_t CuePoint::PayloadSize() const { + uint64_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition, + static_cast<uint64>(cluster_pos_)); + size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_)); + if (output_block_number_ && block_number_ > 1) + size += EbmlElementSize(libwebm::kMkvCueBlockNumber, + static_cast<uint64>(block_number_)); + const uint64_t track_pos_size = + EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size; + const uint64_t payload_size = + EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) + + track_pos_size; + + return payload_size; +} + +uint64_t CuePoint::Size() const { + const uint64_t payload_size = PayloadSize(); + return EbmlMasterElementSize(libwebm::kMkvCuePoint, payload_size) + + payload_size; +} + +/////////////////////////////////////////////////////////////// +// +// Cues Class + +Cues::Cues() + : cue_entries_capacity_(0), + cue_entries_size_(0), + cue_entries_(NULL), + output_block_number_(true) {} + +Cues::~Cues() { + if (cue_entries_) { + for (int32_t i = 0; i < cue_entries_size_; ++i) { + CuePoint* const cue = cue_entries_[i]; + delete cue; + } + delete[] cue_entries_; + } +} + +bool Cues::AddCue(CuePoint* cue) { + if (!cue) + return false; + + if ((cue_entries_size_ + 1) > cue_entries_capacity_) { + // Add more CuePoints. + const int32_t new_capacity = + (!cue_entries_capacity_) ? 2 : cue_entries_capacity_ * 2; + + if (new_capacity < 1) + return false; + + CuePoint** const cues = + new (std::nothrow) CuePoint*[new_capacity]; // NOLINT + if (!cues) + return false; + + for (int32_t i = 0; i < cue_entries_size_; ++i) { + cues[i] = cue_entries_[i]; + } + + delete[] cue_entries_; + + cue_entries_ = cues; + cue_entries_capacity_ = new_capacity; + } + + cue->set_output_block_number(output_block_number_); + cue_entries_[cue_entries_size_++] = cue; + return true; +} + +CuePoint* Cues::GetCueByIndex(int32_t index) const { + if (cue_entries_ == NULL) + return NULL; + + if (index >= cue_entries_size_) + return NULL; + + return cue_entries_[index]; +} + +uint64_t Cues::Size() { + uint64_t size = 0; + for (int32_t i = 0; i < cue_entries_size_; ++i) + size += GetCueByIndex(i)->Size(); + size += EbmlMasterElementSize(libwebm::kMkvCues, size); + return size; +} + +bool Cues::Write(IMkvWriter* writer) const { + if (!writer) + return false; + + uint64_t size = 0; + for (int32_t i = 0; i < cue_entries_size_; ++i) { + const CuePoint* const cue = GetCueByIndex(i); + + if (!cue) + return false; + + size += cue->Size(); + } + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvCues, size)) + return false; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + for (int32_t i = 0; i < cue_entries_size_; ++i) { + const CuePoint* const cue = GetCueByIndex(i); + + if (!cue->Write(writer)) + return false; + } + + const int64_t stop_position = writer->Position(); + if (stop_position < 0) + return false; + + if (stop_position - payload_position != static_cast<int64_t>(size)) + return false; + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// ContentEncAESSettings Class + +ContentEncAESSettings::ContentEncAESSettings() : cipher_mode_(kCTR) {} + +uint64_t ContentEncAESSettings::Size() const { + const uint64_t payload = PayloadSize(); + const uint64_t size = + EbmlMasterElementSize(libwebm::kMkvContentEncAESSettings, payload) + + payload; + return size; +} + +bool ContentEncAESSettings::Write(IMkvWriter* writer) const { + const uint64_t payload = PayloadSize(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncAESSettings, + payload)) + return false; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvAESSettingsCipherMode, + static_cast<uint64>(cipher_mode_))) { + return false; + } + + const int64_t stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64_t>(payload)) + return false; + + return true; +} + +uint64_t ContentEncAESSettings::PayloadSize() const { + uint64_t size = EbmlElementSize(libwebm::kMkvAESSettingsCipherMode, + static_cast<uint64>(cipher_mode_)); + return size; +} + +/////////////////////////////////////////////////////////////// +// +// ContentEncoding Class + +ContentEncoding::ContentEncoding() + : enc_algo_(5), + enc_key_id_(NULL), + encoding_order_(0), + encoding_scope_(1), + encoding_type_(1), + enc_key_id_length_(0) {} + +ContentEncoding::~ContentEncoding() { delete[] enc_key_id_; } + +bool ContentEncoding::SetEncryptionID(const uint8_t* id, uint64_t length) { + if (!id || length < 1) + return false; + + delete[] enc_key_id_; + + enc_key_id_ = + new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT + if (!enc_key_id_) + return false; + + memcpy(enc_key_id_, id, static_cast<size_t>(length)); + enc_key_id_length_ = length; + + return true; +} + +uint64_t ContentEncoding::Size() const { + const uint64_t encryption_size = EncryptionSize(); + const uint64_t encoding_size = EncodingSize(0, encryption_size); + const uint64_t encodings_size = + EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) + + encoding_size; + + return encodings_size; +} + +bool ContentEncoding::Write(IMkvWriter* writer) const { + const uint64_t encryption_size = EncryptionSize(); + const uint64_t encoding_size = EncodingSize(0, encryption_size); + const uint64_t size = + EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) + + encoding_size; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncoding, + encoding_size)) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingOrder, + static_cast<uint64>(encoding_order_))) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingScope, + static_cast<uint64>(encoding_scope_))) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingType, + static_cast<uint64>(encoding_type_))) + return false; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncryption, + encryption_size)) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvContentEncAlgo, + static_cast<uint64>(enc_algo_))) { + return false; + } + if (!WriteEbmlElement(writer, libwebm::kMkvContentEncKeyID, enc_key_id_, + enc_key_id_length_)) + return false; + + if (!enc_aes_settings_.Write(writer)) + return false; + + const int64_t stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64_t>(size)) + return false; + + return true; +} + +uint64_t ContentEncoding::EncodingSize(uint64_t compresion_size, + uint64_t encryption_size) const { + // TODO(fgalligan): Add support for compression settings. + if (compresion_size != 0) + return 0; + + uint64_t encoding_size = 0; + + if (encryption_size > 0) { + encoding_size += + EbmlMasterElementSize(libwebm::kMkvContentEncryption, encryption_size) + + encryption_size; + } + encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingType, + static_cast<uint64>(encoding_type_)); + encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingScope, + static_cast<uint64>(encoding_scope_)); + encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingOrder, + static_cast<uint64>(encoding_order_)); + + return encoding_size; +} + +uint64_t ContentEncoding::EncryptionSize() const { + const uint64_t aes_size = enc_aes_settings_.Size(); + + uint64_t encryption_size = EbmlElementSize(libwebm::kMkvContentEncKeyID, + enc_key_id_, enc_key_id_length_); + encryption_size += EbmlElementSize(libwebm::kMkvContentEncAlgo, + static_cast<uint64>(enc_algo_)); + + return encryption_size + aes_size; +} + +/////////////////////////////////////////////////////////////// +// +// Track Class + +Track::Track(unsigned int* seed) + : codec_id_(NULL), + codec_private_(NULL), + language_(NULL), + max_block_additional_id_(0), + name_(NULL), + number_(0), + type_(0), + uid_(MakeUID(seed)), + codec_delay_(0), + seek_pre_roll_(0), + default_duration_(0), + codec_private_length_(0), + content_encoding_entries_(NULL), + content_encoding_entries_size_(0) {} + +Track::~Track() { + delete[] codec_id_; + delete[] codec_private_; + delete[] language_; + delete[] name_; + + if (content_encoding_entries_) { + for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) { + ContentEncoding* const encoding = content_encoding_entries_[i]; + delete encoding; + } + delete[] content_encoding_entries_; + } +} + +bool Track::AddContentEncoding() { + const uint32_t count = content_encoding_entries_size_ + 1; + + ContentEncoding** const content_encoding_entries = + new (std::nothrow) ContentEncoding*[count]; // NOLINT + if (!content_encoding_entries) + return false; + + ContentEncoding* const content_encoding = + new (std::nothrow) ContentEncoding(); // NOLINT + if (!content_encoding) { + delete[] content_encoding_entries; + return false; + } + + for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) { + content_encoding_entries[i] = content_encoding_entries_[i]; + } + + delete[] content_encoding_entries_; + + content_encoding_entries_ = content_encoding_entries; + content_encoding_entries_[content_encoding_entries_size_] = content_encoding; + content_encoding_entries_size_ = count; + return true; +} + +ContentEncoding* Track::GetContentEncodingByIndex(uint32_t index) const { + if (content_encoding_entries_ == NULL) + return NULL; + + if (index >= content_encoding_entries_size_) + return NULL; + + return content_encoding_entries_[index]; +} + +uint64_t Track::PayloadSize() const { + uint64_t size = + EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_)); + size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_)); + size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_)); + if (codec_id_) + size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_); + if (codec_private_) + size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_, + codec_private_length_); + if (language_) + size += EbmlElementSize(libwebm::kMkvLanguage, language_); + if (name_) + size += EbmlElementSize(libwebm::kMkvName, name_); + if (max_block_additional_id_) { + size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID, + static_cast<uint64>(max_block_additional_id_)); + } + if (codec_delay_) { + size += EbmlElementSize(libwebm::kMkvCodecDelay, + static_cast<uint64>(codec_delay_)); + } + if (seek_pre_roll_) { + size += EbmlElementSize(libwebm::kMkvSeekPreRoll, + static_cast<uint64>(seek_pre_roll_)); + } + if (default_duration_) { + size += EbmlElementSize(libwebm::kMkvDefaultDuration, + static_cast<uint64>(default_duration_)); + } + + if (content_encoding_entries_size_ > 0) { + uint64_t content_encodings_size = 0; + for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) { + ContentEncoding* const encoding = content_encoding_entries_[i]; + content_encodings_size += encoding->Size(); + } + + size += EbmlMasterElementSize(libwebm::kMkvContentEncodings, + content_encodings_size) + + content_encodings_size; + } + + return size; +} + +uint64_t Track::Size() const { + uint64_t size = PayloadSize(); + size += EbmlMasterElementSize(libwebm::kMkvTrackEntry, size); + return size; +} + +bool Track::Write(IMkvWriter* writer) const { + if (!writer) + return false; + + // mandatory elements without a default value. + if (!type_ || !codec_id_) + return false; + + // AV1 tracks require a CodecPrivate. See + // https://github.com/Matroska-Org/matroska-specification/blob/av1-mappin/codec/av1.md + // TODO(tomfinegan): Update the above link to the AV1 Matroska mappings to + // point to a stable version once it is finalized, or our own WebM mappings + // page on webmproject.org should we decide to release them. + if (!strcmp(codec_id_, Tracks::kAv1CodecId) && !codec_private_) + return false; + + // |size| may be bigger than what is written out in this function because + // derived classes may write out more data in the Track element. + const uint64_t payload_size = PayloadSize(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvTrackEntry, payload_size)) + return false; + + uint64_t size = + EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_)); + size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_)); + size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_)); + if (codec_id_) + size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_); + if (codec_private_) + size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_, + static_cast<uint64>(codec_private_length_)); + if (language_) + size += EbmlElementSize(libwebm::kMkvLanguage, language_); + if (name_) + size += EbmlElementSize(libwebm::kMkvName, name_); + if (max_block_additional_id_) + size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID, + static_cast<uint64>(max_block_additional_id_)); + if (codec_delay_) + size += EbmlElementSize(libwebm::kMkvCodecDelay, + static_cast<uint64>(codec_delay_)); + if (seek_pre_roll_) + size += EbmlElementSize(libwebm::kMkvSeekPreRoll, + static_cast<uint64>(seek_pre_roll_)); + if (default_duration_) + size += EbmlElementSize(libwebm::kMkvDefaultDuration, + static_cast<uint64>(default_duration_)); + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvTrackNumber, + static_cast<uint64>(number_))) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvTrackUID, + static_cast<uint64>(uid_))) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvTrackType, + static_cast<uint64>(type_))) + return false; + if (max_block_additional_id_) { + if (!WriteEbmlElement(writer, libwebm::kMkvMaxBlockAdditionID, + static_cast<uint64>(max_block_additional_id_))) { + return false; + } + } + if (codec_delay_) { + if (!WriteEbmlElement(writer, libwebm::kMkvCodecDelay, + static_cast<uint64>(codec_delay_))) + return false; + } + if (seek_pre_roll_) { + if (!WriteEbmlElement(writer, libwebm::kMkvSeekPreRoll, + static_cast<uint64>(seek_pre_roll_))) + return false; + } + if (default_duration_) { + if (!WriteEbmlElement(writer, libwebm::kMkvDefaultDuration, + static_cast<uint64>(default_duration_))) + return false; + } + if (codec_id_) { + if (!WriteEbmlElement(writer, libwebm::kMkvCodecID, codec_id_)) + return false; + } + if (codec_private_) { + if (!WriteEbmlElement(writer, libwebm::kMkvCodecPrivate, codec_private_, + static_cast<uint64>(codec_private_length_))) + return false; + } + if (language_) { + if (!WriteEbmlElement(writer, libwebm::kMkvLanguage, language_)) + return false; + } + if (name_) { + if (!WriteEbmlElement(writer, libwebm::kMkvName, name_)) + return false; + } + + int64_t stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64_t>(size)) + return false; + + if (content_encoding_entries_size_ > 0) { + uint64_t content_encodings_size = 0; + for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) { + ContentEncoding* const encoding = content_encoding_entries_[i]; + content_encodings_size += encoding->Size(); + } + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncodings, + content_encodings_size)) + return false; + + for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) { + ContentEncoding* const encoding = content_encoding_entries_[i]; + if (!encoding->Write(writer)) + return false; + } + } + + stop_position = writer->Position(); + if (stop_position < 0) + return false; + return true; +} + +bool Track::SetCodecPrivate(const uint8_t* codec_private, uint64_t length) { + if (!codec_private || length < 1) + return false; + + delete[] codec_private_; + + codec_private_ = + new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT + if (!codec_private_) + return false; + + memcpy(codec_private_, codec_private, static_cast<size_t>(length)); + codec_private_length_ = length; + + return true; +} + +void Track::set_codec_id(const char* codec_id) { + if (codec_id) { + delete[] codec_id_; + + const size_t length = strlen(codec_id) + 1; + codec_id_ = new (std::nothrow) char[length]; // NOLINT + if (codec_id_) { +#ifdef _MSC_VER + strcpy_s(codec_id_, length, codec_id); +#else + strcpy(codec_id_, codec_id); +#endif + } + } +} + +// TODO(fgalligan): Vet the language parameter. +void Track::set_language(const char* language) { + if (language) { + delete[] language_; + + const size_t length = strlen(language) + 1; + language_ = new (std::nothrow) char[length]; // NOLINT + if (language_) { +#ifdef _MSC_VER + strcpy_s(language_, length, language); +#else + strcpy(language_, language); +#endif + } + } +} + +void Track::set_name(const char* name) { + if (name) { + delete[] name_; + + const size_t length = strlen(name) + 1; + name_ = new (std::nothrow) char[length]; // NOLINT + if (name_) { +#ifdef _MSC_VER + strcpy_s(name_, length, name); +#else + strcpy(name_, name); +#endif + } + } +} + +/////////////////////////////////////////////////////////////// +// +// Colour and its child elements + +uint64_t PrimaryChromaticity::PrimaryChromaticitySize( + libwebm::MkvId x_id, libwebm::MkvId y_id) const { + return EbmlElementSize(x_id, x_) + EbmlElementSize(y_id, y_); +} + +bool PrimaryChromaticity::Write(IMkvWriter* writer, libwebm::MkvId x_id, + libwebm::MkvId y_id) const { + if (!Valid()) { + return false; + } + return WriteEbmlElement(writer, x_id, x_) && + WriteEbmlElement(writer, y_id, y_); +} + +bool PrimaryChromaticity::Valid() const { + return (x_ >= kChromaticityMin && x_ <= kChromaticityMax && + y_ >= kChromaticityMin && y_ <= kChromaticityMax); +} + +uint64_t MasteringMetadata::MasteringMetadataSize() const { + uint64_t size = PayloadSize(); + + if (size > 0) + size += EbmlMasterElementSize(libwebm::kMkvMasteringMetadata, size); + + return size; +} + +bool MasteringMetadata::Valid() const { + if (luminance_min_ != kValueNotPresent) { + if (luminance_min_ < kMinLuminance || luminance_min_ > kMinLuminanceMax || + luminance_min_ > luminance_max_) { + return false; + } + } + if (luminance_max_ != kValueNotPresent) { + if (luminance_max_ < kMinLuminance || luminance_max_ > kMaxLuminanceMax || + luminance_max_ < luminance_min_) { + return false; + } + } + if (r_ && !r_->Valid()) + return false; + if (g_ && !g_->Valid()) + return false; + if (b_ && !b_->Valid()) + return false; + if (white_point_ && !white_point_->Valid()) + return false; + + return true; +} + +bool MasteringMetadata::Write(IMkvWriter* writer) const { + const uint64_t size = PayloadSize(); + + // Don't write an empty element. + if (size == 0) + return true; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvMasteringMetadata, size)) + return false; + if (luminance_max_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvLuminanceMax, luminance_max_)) { + return false; + } + if (luminance_min_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvLuminanceMin, luminance_min_)) { + return false; + } + if (r_ && !r_->Write(writer, libwebm::kMkvPrimaryRChromaticityX, + libwebm::kMkvPrimaryRChromaticityY)) { + return false; + } + if (g_ && !g_->Write(writer, libwebm::kMkvPrimaryGChromaticityX, + libwebm::kMkvPrimaryGChromaticityY)) { + return false; + } + if (b_ && !b_->Write(writer, libwebm::kMkvPrimaryBChromaticityX, + libwebm::kMkvPrimaryBChromaticityY)) { + return false; + } + if (white_point_ && + !white_point_->Write(writer, libwebm::kMkvWhitePointChromaticityX, + libwebm::kMkvWhitePointChromaticityY)) { + return false; + } + + return true; +} + +bool MasteringMetadata::SetChromaticity( + const PrimaryChromaticity* r, const PrimaryChromaticity* g, + const PrimaryChromaticity* b, const PrimaryChromaticity* white_point) { + PrimaryChromaticityPtr r_ptr(nullptr); + if (r) { + if (!CopyChromaticity(r, &r_ptr)) + return false; + } + PrimaryChromaticityPtr g_ptr(nullptr); + if (g) { + if (!CopyChromaticity(g, &g_ptr)) + return false; + } + PrimaryChromaticityPtr b_ptr(nullptr); + if (b) { + if (!CopyChromaticity(b, &b_ptr)) + return false; + } + PrimaryChromaticityPtr wp_ptr(nullptr); + if (white_point) { + if (!CopyChromaticity(white_point, &wp_ptr)) + return false; + } + + r_ = r_ptr.release(); + g_ = g_ptr.release(); + b_ = b_ptr.release(); + white_point_ = wp_ptr.release(); + return true; +} + +uint64_t MasteringMetadata::PayloadSize() const { + uint64_t size = 0; + + if (luminance_max_ != kValueNotPresent) + size += EbmlElementSize(libwebm::kMkvLuminanceMax, luminance_max_); + if (luminance_min_ != kValueNotPresent) + size += EbmlElementSize(libwebm::kMkvLuminanceMin, luminance_min_); + + if (r_) { + size += r_->PrimaryChromaticitySize(libwebm::kMkvPrimaryRChromaticityX, + libwebm::kMkvPrimaryRChromaticityY); + } + if (g_) { + size += g_->PrimaryChromaticitySize(libwebm::kMkvPrimaryGChromaticityX, + libwebm::kMkvPrimaryGChromaticityY); + } + if (b_) { + size += b_->PrimaryChromaticitySize(libwebm::kMkvPrimaryBChromaticityX, + libwebm::kMkvPrimaryBChromaticityY); + } + if (white_point_) { + size += white_point_->PrimaryChromaticitySize( + libwebm::kMkvWhitePointChromaticityX, + libwebm::kMkvWhitePointChromaticityY); + } + + return size; +} + +uint64_t Colour::ColourSize() const { + uint64_t size = PayloadSize(); + + if (size > 0) + size += EbmlMasterElementSize(libwebm::kMkvColour, size); + + return size; +} + +bool Colour::Valid() const { + if (mastering_metadata_ && !mastering_metadata_->Valid()) + return false; + if (matrix_coefficients_ != kValueNotPresent && + !IsMatrixCoefficientsValueValid(matrix_coefficients_)) { + return false; + } + if (chroma_siting_horz_ != kValueNotPresent && + !IsChromaSitingHorzValueValid(chroma_siting_horz_)) { + return false; + } + if (chroma_siting_vert_ != kValueNotPresent && + !IsChromaSitingVertValueValid(chroma_siting_vert_)) { + return false; + } + if (range_ != kValueNotPresent && !IsColourRangeValueValid(range_)) + return false; + if (transfer_characteristics_ != kValueNotPresent && + !IsTransferCharacteristicsValueValid(transfer_characteristics_)) { + return false; + } + if (primaries_ != kValueNotPresent && !IsPrimariesValueValid(primaries_)) + return false; + + return true; +} + +bool Colour::Write(IMkvWriter* writer) const { + const uint64_t size = PayloadSize(); + + // Don't write an empty element. + if (size == 0) + return true; + + // Don't write an invalid element. + if (!Valid()) + return false; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvColour, size)) + return false; + + if (matrix_coefficients_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvMatrixCoefficients, + static_cast<uint64>(matrix_coefficients_))) { + return false; + } + if (bits_per_channel_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvBitsPerChannel, + static_cast<uint64>(bits_per_channel_))) { + return false; + } + if (chroma_subsampling_horz_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingHorz, + static_cast<uint64>(chroma_subsampling_horz_))) { + return false; + } + if (chroma_subsampling_vert_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingVert, + static_cast<uint64>(chroma_subsampling_vert_))) { + return false; + } + + if (cb_subsampling_horz_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingHorz, + static_cast<uint64>(cb_subsampling_horz_))) { + return false; + } + if (cb_subsampling_vert_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingVert, + static_cast<uint64>(cb_subsampling_vert_))) { + return false; + } + if (chroma_siting_horz_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvChromaSitingHorz, + static_cast<uint64>(chroma_siting_horz_))) { + return false; + } + if (chroma_siting_vert_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvChromaSitingVert, + static_cast<uint64>(chroma_siting_vert_))) { + return false; + } + if (range_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvRange, + static_cast<uint64>(range_))) { + return false; + } + if (transfer_characteristics_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvTransferCharacteristics, + static_cast<uint64>(transfer_characteristics_))) { + return false; + } + if (primaries_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvPrimaries, + static_cast<uint64>(primaries_))) { + return false; + } + if (max_cll_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvMaxCLL, + static_cast<uint64>(max_cll_))) { + return false; + } + if (max_fall_ != kValueNotPresent && + !WriteEbmlElement(writer, libwebm::kMkvMaxFALL, + static_cast<uint64>(max_fall_))) { + return false; + } + + if (mastering_metadata_ && !mastering_metadata_->Write(writer)) + return false; + + return true; +} + +bool Colour::SetMasteringMetadata(const MasteringMetadata& mastering_metadata) { + std::unique_ptr<MasteringMetadata> mm_ptr(new MasteringMetadata()); + if (!mm_ptr.get()) + return false; + + mm_ptr->set_luminance_max(mastering_metadata.luminance_max()); + mm_ptr->set_luminance_min(mastering_metadata.luminance_min()); + + if (!mm_ptr->SetChromaticity(mastering_metadata.r(), mastering_metadata.g(), + mastering_metadata.b(), + mastering_metadata.white_point())) { + return false; + } + + delete mastering_metadata_; + mastering_metadata_ = mm_ptr.release(); + return true; +} + +uint64_t Colour::PayloadSize() const { + uint64_t size = 0; + + if (matrix_coefficients_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvMatrixCoefficients, + static_cast<uint64>(matrix_coefficients_)); + } + if (bits_per_channel_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvBitsPerChannel, + static_cast<uint64>(bits_per_channel_)); + } + if (chroma_subsampling_horz_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvChromaSubsamplingHorz, + static_cast<uint64>(chroma_subsampling_horz_)); + } + if (chroma_subsampling_vert_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvChromaSubsamplingVert, + static_cast<uint64>(chroma_subsampling_vert_)); + } + if (cb_subsampling_horz_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvCbSubsamplingHorz, + static_cast<uint64>(cb_subsampling_horz_)); + } + if (cb_subsampling_vert_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvCbSubsamplingVert, + static_cast<uint64>(cb_subsampling_vert_)); + } + if (chroma_siting_horz_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvChromaSitingHorz, + static_cast<uint64>(chroma_siting_horz_)); + } + if (chroma_siting_vert_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvChromaSitingVert, + static_cast<uint64>(chroma_siting_vert_)); + } + if (range_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvRange, static_cast<uint64>(range_)); + } + if (transfer_characteristics_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvTransferCharacteristics, + static_cast<uint64>(transfer_characteristics_)); + } + if (primaries_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvPrimaries, + static_cast<uint64>(primaries_)); + } + if (max_cll_ != kValueNotPresent) { + size += EbmlElementSize(libwebm::kMkvMaxCLL, static_cast<uint64>(max_cll_)); + } + if (max_fall_ != kValueNotPresent) { + size += + EbmlElementSize(libwebm::kMkvMaxFALL, static_cast<uint64>(max_fall_)); + } + + if (mastering_metadata_) + size += mastering_metadata_->MasteringMetadataSize(); + + return size; +} + +/////////////////////////////////////////////////////////////// +// +// Projection element + +uint64_t Projection::ProjectionSize() const { + uint64_t size = PayloadSize(); + + if (size > 0) + size += EbmlMasterElementSize(libwebm::kMkvProjection, size); + + return size; +} + +bool Projection::Write(IMkvWriter* writer) const { + const uint64_t size = PayloadSize(); + + // Don't write an empty element. + if (size == 0) + return true; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvProjection, size)) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvProjectionType, + static_cast<uint64>(type_))) { + return false; + } + + if (private_data_length_ > 0 && private_data_ != NULL && + !WriteEbmlElement(writer, libwebm::kMkvProjectionPrivate, private_data_, + private_data_length_)) { + return false; + } + + if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseYaw, pose_yaw_)) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPosePitch, + pose_pitch_)) { + return false; + } + + if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseRoll, pose_roll_)) { + return false; + } + + return true; +} + +bool Projection::SetProjectionPrivate(const uint8_t* data, + uint64_t data_length) { + if (data == NULL || data_length == 0) { + return false; + } + + if (data_length != static_cast<size_t>(data_length)) { + return false; + } + + uint8_t* new_private_data = + new (std::nothrow) uint8_t[static_cast<size_t>(data_length)]; + if (new_private_data == NULL) { + return false; + } + + delete[] private_data_; + private_data_ = new_private_data; + private_data_length_ = data_length; + memcpy(private_data_, data, static_cast<size_t>(data_length)); + + return true; +} + +uint64_t Projection::PayloadSize() const { + uint64_t size = + EbmlElementSize(libwebm::kMkvProjection, static_cast<uint64>(type_)); + + if (private_data_length_ > 0 && private_data_ != NULL) { + size += EbmlElementSize(libwebm::kMkvProjectionPrivate, private_data_, + private_data_length_); + } + + size += EbmlElementSize(libwebm::kMkvProjectionPoseYaw, pose_yaw_); + size += EbmlElementSize(libwebm::kMkvProjectionPosePitch, pose_pitch_); + size += EbmlElementSize(libwebm::kMkvProjectionPoseRoll, pose_roll_); + + return size; +} + +/////////////////////////////////////////////////////////////// +// +// VideoTrack Class + +VideoTrack::VideoTrack(unsigned int* seed) + : Track(seed), + display_height_(0), + display_width_(0), + pixel_height_(0), + pixel_width_(0), + crop_left_(0), + crop_right_(0), + crop_top_(0), + crop_bottom_(0), + frame_rate_(0.0), + height_(0), + stereo_mode_(0), + alpha_mode_(0), + width_(0), + colour_space_(NULL), + colour_(NULL), + projection_(NULL) {} + +VideoTrack::~VideoTrack() { + delete colour_; + delete projection_; +} + +bool VideoTrack::SetStereoMode(uint64_t stereo_mode) { + if (stereo_mode != kMono && stereo_mode != kSideBySideLeftIsFirst && + stereo_mode != kTopBottomRightIsFirst && + stereo_mode != kTopBottomLeftIsFirst && + stereo_mode != kSideBySideRightIsFirst) + return false; + + stereo_mode_ = stereo_mode; + return true; +} + +bool VideoTrack::SetAlphaMode(uint64_t alpha_mode) { + if (alpha_mode != kNoAlpha && alpha_mode != kAlpha) + return false; + + alpha_mode_ = alpha_mode; + return true; +} + +uint64_t VideoTrack::PayloadSize() const { + const uint64_t parent_size = Track::PayloadSize(); + + uint64_t size = VideoPayloadSize(); + size += EbmlMasterElementSize(libwebm::kMkvVideo, size); + + return parent_size + size; +} + +bool VideoTrack::Write(IMkvWriter* writer) const { + if (!Track::Write(writer)) + return false; + + const uint64_t size = VideoPayloadSize(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvVideo, size)) + return false; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement( + writer, libwebm::kMkvPixelWidth, + static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_))) + return false; + if (!WriteEbmlElement( + writer, libwebm::kMkvPixelHeight, + static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_))) + return false; + if (display_width_ > 0) { + if (!WriteEbmlElement(writer, libwebm::kMkvDisplayWidth, + static_cast<uint64>(display_width_))) + return false; + } + if (display_height_ > 0) { + if (!WriteEbmlElement(writer, libwebm::kMkvDisplayHeight, + static_cast<uint64>(display_height_))) + return false; + } + if (crop_left_ > 0) { + if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropLeft, + static_cast<uint64>(crop_left_))) + return false; + } + if (crop_right_ > 0) { + if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropRight, + static_cast<uint64>(crop_right_))) + return false; + } + if (crop_top_ > 0) { + if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropTop, + static_cast<uint64>(crop_top_))) + return false; + } + if (crop_bottom_ > 0) { + if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropBottom, + static_cast<uint64>(crop_bottom_))) + return false; + } + if (stereo_mode_ > kMono) { + if (!WriteEbmlElement(writer, libwebm::kMkvStereoMode, + static_cast<uint64>(stereo_mode_))) + return false; + } + if (alpha_mode_ > kNoAlpha) { + if (!WriteEbmlElement(writer, libwebm::kMkvAlphaMode, + static_cast<uint64>(alpha_mode_))) + return false; + } + if (colour_space_) { + if (!WriteEbmlElement(writer, libwebm::kMkvColourSpace, colour_space_)) + return false; + } + if (frame_rate_ > 0.0) { + if (!WriteEbmlElement(writer, libwebm::kMkvFrameRate, + static_cast<float>(frame_rate_))) { + return false; + } + } + if (colour_) { + if (!colour_->Write(writer)) + return false; + } + if (projection_) { + if (!projection_->Write(writer)) + return false; + } + + const int64_t stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64_t>(size)) { + return false; + } + + return true; +} + +void VideoTrack::set_colour_space(const char* colour_space) { + if (colour_space) { + delete[] colour_space_; + + const size_t length = strlen(colour_space) + 1; + colour_space_ = new (std::nothrow) char[length]; // NOLINT + if (colour_space_) { +#ifdef _MSC_VER + strcpy_s(colour_space_, length, colour_space); +#else + strcpy(colour_space_, colour_space); +#endif + } + } +} + +bool VideoTrack::SetColour(const Colour& colour) { + std::unique_ptr<Colour> colour_ptr(new Colour()); + if (!colour_ptr.get()) + return false; + + if (colour.mastering_metadata()) { + if (!colour_ptr->SetMasteringMetadata(*colour.mastering_metadata())) + return false; + } + + colour_ptr->set_matrix_coefficients(colour.matrix_coefficients()); + colour_ptr->set_bits_per_channel(colour.bits_per_channel()); + colour_ptr->set_chroma_subsampling_horz(colour.chroma_subsampling_horz()); + colour_ptr->set_chroma_subsampling_vert(colour.chroma_subsampling_vert()); + colour_ptr->set_cb_subsampling_horz(colour.cb_subsampling_horz()); + colour_ptr->set_cb_subsampling_vert(colour.cb_subsampling_vert()); + colour_ptr->set_chroma_siting_horz(colour.chroma_siting_horz()); + colour_ptr->set_chroma_siting_vert(colour.chroma_siting_vert()); + colour_ptr->set_range(colour.range()); + colour_ptr->set_transfer_characteristics(colour.transfer_characteristics()); + colour_ptr->set_primaries(colour.primaries()); + colour_ptr->set_max_cll(colour.max_cll()); + colour_ptr->set_max_fall(colour.max_fall()); + delete colour_; + colour_ = colour_ptr.release(); + return true; +} + +bool VideoTrack::SetProjection(const Projection& projection) { + std::unique_ptr<Projection> projection_ptr(new Projection()); + if (!projection_ptr.get()) + return false; + + if (projection.private_data()) { + if (!projection_ptr->SetProjectionPrivate( + projection.private_data(), projection.private_data_length())) { + return false; + } + } + + projection_ptr->set_type(projection.type()); + projection_ptr->set_pose_yaw(projection.pose_yaw()); + projection_ptr->set_pose_pitch(projection.pose_pitch()); + projection_ptr->set_pose_roll(projection.pose_roll()); + delete projection_; + projection_ = projection_ptr.release(); + return true; +} + +uint64_t VideoTrack::VideoPayloadSize() const { + uint64_t size = EbmlElementSize( + libwebm::kMkvPixelWidth, + static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_)); + size += EbmlElementSize( + libwebm::kMkvPixelHeight, + static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_)); + if (display_width_ > 0) + size += EbmlElementSize(libwebm::kMkvDisplayWidth, + static_cast<uint64>(display_width_)); + if (display_height_ > 0) + size += EbmlElementSize(libwebm::kMkvDisplayHeight, + static_cast<uint64>(display_height_)); + if (crop_left_ > 0) + size += EbmlElementSize(libwebm::kMkvPixelCropLeft, + static_cast<uint64>(crop_left_)); + if (crop_right_ > 0) + size += EbmlElementSize(libwebm::kMkvPixelCropRight, + static_cast<uint64>(crop_right_)); + if (crop_top_ > 0) + size += EbmlElementSize(libwebm::kMkvPixelCropTop, + static_cast<uint64>(crop_top_)); + if (crop_bottom_ > 0) + size += EbmlElementSize(libwebm::kMkvPixelCropBottom, + static_cast<uint64>(crop_bottom_)); + if (stereo_mode_ > kMono) + size += EbmlElementSize(libwebm::kMkvStereoMode, + static_cast<uint64>(stereo_mode_)); + if (alpha_mode_ > kNoAlpha) + size += EbmlElementSize(libwebm::kMkvAlphaMode, + static_cast<uint64>(alpha_mode_)); + if (frame_rate_ > 0.0) + size += EbmlElementSize(libwebm::kMkvFrameRate, + static_cast<float>(frame_rate_)); + if (colour_space_) + size += EbmlElementSize(libwebm::kMkvColourSpace, colour_space_); + if (colour_) + size += colour_->ColourSize(); + if (projection_) + size += projection_->ProjectionSize(); + + return size; +} + +/////////////////////////////////////////////////////////////// +// +// AudioTrack Class + +AudioTrack::AudioTrack(unsigned int* seed) + : Track(seed), bit_depth_(0), channels_(1), sample_rate_(0.0) {} + +AudioTrack::~AudioTrack() {} + +uint64_t AudioTrack::PayloadSize() const { + const uint64_t parent_size = Track::PayloadSize(); + + uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency, + static_cast<float>(sample_rate_)); + size += + EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_)); + if (bit_depth_ > 0) + size += + EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_)); + size += EbmlMasterElementSize(libwebm::kMkvAudio, size); + + return parent_size + size; +} + +bool AudioTrack::Write(IMkvWriter* writer) const { + if (!Track::Write(writer)) + return false; + + // Calculate AudioSettings size. + uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency, + static_cast<float>(sample_rate_)); + size += + EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_)); + if (bit_depth_ > 0) + size += + EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_)); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvAudio, size)) + return false; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvSamplingFrequency, + static_cast<float>(sample_rate_))) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvChannels, + static_cast<uint64>(channels_))) + return false; + if (bit_depth_ > 0) + if (!WriteEbmlElement(writer, libwebm::kMkvBitDepth, + static_cast<uint64>(bit_depth_))) + return false; + + const int64_t stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64_t>(size)) + return false; + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Tracks Class + +const char Tracks::kOpusCodecId[] = "A_OPUS"; +const char Tracks::kVorbisCodecId[] = "A_VORBIS"; +const char Tracks::kAv1CodecId[] = "V_AV1"; +const char Tracks::kVp8CodecId[] = "V_VP8"; +const char Tracks::kVp9CodecId[] = "V_VP9"; +const char Tracks::kWebVttCaptionsId[] = "D_WEBVTT/CAPTIONS"; +const char Tracks::kWebVttDescriptionsId[] = "D_WEBVTT/DESCRIPTIONS"; +const char Tracks::kWebVttMetadataId[] = "D_WEBVTT/METADATA"; +const char Tracks::kWebVttSubtitlesId[] = "D_WEBVTT/SUBTITLES"; + +Tracks::Tracks() + : track_entries_(NULL), track_entries_size_(0), wrote_tracks_(false) {} + +Tracks::~Tracks() { + if (track_entries_) { + for (uint32_t i = 0; i < track_entries_size_; ++i) { + Track* const track = track_entries_[i]; + delete track; + } + delete[] track_entries_; + } +} + +bool Tracks::AddTrack(Track* track, int32_t number) { + if (number < 0 || wrote_tracks_) + return false; + + // This muxer only supports track numbers in the range [1, 126], in + // order to be able (to use Matroska integer representation) to + // serialize the block header (of which the track number is a part) + // for a frame using exactly 4 bytes. + + if (number > 0x7E) + return false; + + uint32_t track_num = number; + + if (track_num > 0) { + // Check to make sure a track does not already have |track_num|. + for (uint32_t i = 0; i < track_entries_size_; ++i) { + if (track_entries_[i]->number() == track_num) + return false; + } + } + + const uint32_t count = track_entries_size_ + 1; + + Track** const track_entries = new (std::nothrow) Track*[count]; // NOLINT + if (!track_entries) + return false; + + for (uint32_t i = 0; i < track_entries_size_; ++i) { + track_entries[i] = track_entries_[i]; + } + + delete[] track_entries_; + + // Find the lowest availible track number > 0. + if (track_num == 0) { + track_num = count; + + // Check to make sure a track does not already have |track_num|. + bool exit = false; + do { + exit = true; + for (uint32_t i = 0; i < track_entries_size_; ++i) { + if (track_entries[i]->number() == track_num) { + track_num++; + exit = false; + break; + } + } + } while (!exit); + } + track->set_number(track_num); + + track_entries_ = track_entries; + track_entries_[track_entries_size_] = track; + track_entries_size_ = count; + return true; +} + +const Track* Tracks::GetTrackByIndex(uint32_t index) const { + if (track_entries_ == NULL) + return NULL; + + if (index >= track_entries_size_) + return NULL; + + return track_entries_[index]; +} + +Track* Tracks::GetTrackByNumber(uint64_t track_number) const { + const int32_t count = track_entries_size(); + for (int32_t i = 0; i < count; ++i) { + if (track_entries_[i]->number() == track_number) + return track_entries_[i]; + } + + return NULL; +} + +bool Tracks::TrackIsAudio(uint64_t track_number) const { + const Track* const track = GetTrackByNumber(track_number); + + if (track->type() == kAudio) + return true; + + return false; +} + +bool Tracks::TrackIsVideo(uint64_t track_number) const { + const Track* const track = GetTrackByNumber(track_number); + + if (track->type() == kVideo) + return true; + + return false; +} + +bool Tracks::Write(IMkvWriter* writer) const { + uint64_t size = 0; + const int32_t count = track_entries_size(); + for (int32_t i = 0; i < count; ++i) { + const Track* const track = GetTrackByIndex(i); + + if (!track) + return false; + + size += track->Size(); + } + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvTracks, size)) + return false; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + for (int32_t i = 0; i < count; ++i) { + const Track* const track = GetTrackByIndex(i); + if (!track->Write(writer)) + return false; + } + + const int64_t stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64_t>(size)) + return false; + + wrote_tracks_ = true; + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Chapter Class + +bool Chapter::set_id(const char* id) { return StrCpy(id, &id_); } + +void Chapter::set_time(const Segment& segment, uint64_t start_ns, + uint64_t end_ns) { + const SegmentInfo* const info = segment.GetSegmentInfo(); + const uint64_t timecode_scale = info->timecode_scale(); + start_timecode_ = start_ns / timecode_scale; + end_timecode_ = end_ns / timecode_scale; +} + +bool Chapter::add_string(const char* title, const char* language, + const char* country) { + if (!ExpandDisplaysArray()) + return false; + + Display& d = displays_[displays_count_++]; + d.Init(); + + if (!d.set_title(title)) + return false; + + if (!d.set_language(language)) + return false; + + if (!d.set_country(country)) + return false; + + return true; +} + +Chapter::Chapter() { + // This ctor only constructs the object. Proper initialization is + // done in Init() (called in Chapters::AddChapter()). The only + // reason we bother implementing this ctor is because we had to + // declare it as private (along with the dtor), in order to prevent + // clients from creating Chapter instances (a privelege we grant + // only to the Chapters class). Doing no initialization here also + // means that creating arrays of chapter objects is more efficient, + // because we only initialize each new chapter object as it becomes + // active on the array. +} + +Chapter::~Chapter() {} + +void Chapter::Init(unsigned int* seed) { + id_ = NULL; + start_timecode_ = 0; + end_timecode_ = 0; + displays_ = NULL; + displays_size_ = 0; + displays_count_ = 0; + uid_ = MakeUID(seed); +} + +void Chapter::ShallowCopy(Chapter* dst) const { + dst->id_ = id_; + dst->start_timecode_ = start_timecode_; + dst->end_timecode_ = end_timecode_; + dst->uid_ = uid_; + dst->displays_ = displays_; + dst->displays_size_ = displays_size_; + dst->displays_count_ = displays_count_; +} + +void Chapter::Clear() { + StrCpy(NULL, &id_); + + while (displays_count_ > 0) { + Display& d = displays_[--displays_count_]; + d.Clear(); + } + + delete[] displays_; + displays_ = NULL; + + displays_size_ = 0; +} + +bool Chapter::ExpandDisplaysArray() { + if (displays_size_ > displays_count_) + return true; // nothing to do yet + + const int size = (displays_size_ == 0) ? 1 : 2 * displays_size_; + + Display* const displays = new (std::nothrow) Display[size]; // NOLINT + if (displays == NULL) + return false; + + for (int idx = 0; idx < displays_count_; ++idx) { + displays[idx] = displays_[idx]; // shallow copy + } + + delete[] displays_; + + displays_ = displays; + displays_size_ = size; + + return true; +} + +uint64_t Chapter::WriteAtom(IMkvWriter* writer) const { + uint64_t payload_size = + EbmlElementSize(libwebm::kMkvChapterStringUID, id_) + + EbmlElementSize(libwebm::kMkvChapterUID, static_cast<uint64>(uid_)) + + EbmlElementSize(libwebm::kMkvChapterTimeStart, + static_cast<uint64>(start_timecode_)) + + EbmlElementSize(libwebm::kMkvChapterTimeEnd, + static_cast<uint64>(end_timecode_)); + + for (int idx = 0; idx < displays_count_; ++idx) { + const Display& d = displays_[idx]; + payload_size += d.WriteDisplay(NULL); + } + + const uint64_t atom_size = + EbmlMasterElementSize(libwebm::kMkvChapterAtom, payload_size) + + payload_size; + + if (writer == NULL) + return atom_size; + + const int64_t start = writer->Position(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterAtom, payload_size)) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvChapterStringUID, id_)) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvChapterUID, + static_cast<uint64>(uid_))) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeStart, + static_cast<uint64>(start_timecode_))) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeEnd, + static_cast<uint64>(end_timecode_))) + return 0; + + for (int idx = 0; idx < displays_count_; ++idx) { + const Display& d = displays_[idx]; + + if (!d.WriteDisplay(writer)) + return 0; + } + + const int64_t stop = writer->Position(); + + if (stop >= start && uint64_t(stop - start) != atom_size) + return 0; + + return atom_size; +} + +void Chapter::Display::Init() { + title_ = NULL; + language_ = NULL; + country_ = NULL; +} + +void Chapter::Display::Clear() { + StrCpy(NULL, &title_); + StrCpy(NULL, &language_); + StrCpy(NULL, &country_); +} + +bool Chapter::Display::set_title(const char* title) { + return StrCpy(title, &title_); +} + +bool Chapter::Display::set_language(const char* language) { + return StrCpy(language, &language_); +} + +bool Chapter::Display::set_country(const char* country) { + return StrCpy(country, &country_); +} + +uint64_t Chapter::Display::WriteDisplay(IMkvWriter* writer) const { + uint64_t payload_size = EbmlElementSize(libwebm::kMkvChapString, title_); + + if (language_) + payload_size += EbmlElementSize(libwebm::kMkvChapLanguage, language_); + + if (country_) + payload_size += EbmlElementSize(libwebm::kMkvChapCountry, country_); + + const uint64_t display_size = + EbmlMasterElementSize(libwebm::kMkvChapterDisplay, payload_size) + + payload_size; + + if (writer == NULL) + return display_size; + + const int64_t start = writer->Position(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterDisplay, + payload_size)) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvChapString, title_)) + return 0; + + if (language_) { + if (!WriteEbmlElement(writer, libwebm::kMkvChapLanguage, language_)) + return 0; + } + + if (country_) { + if (!WriteEbmlElement(writer, libwebm::kMkvChapCountry, country_)) + return 0; + } + + const int64_t stop = writer->Position(); + + if (stop >= start && uint64_t(stop - start) != display_size) + return 0; + + return display_size; +} + +/////////////////////////////////////////////////////////////// +// +// Chapters Class + +Chapters::Chapters() : chapters_size_(0), chapters_count_(0), chapters_(NULL) {} + +Chapters::~Chapters() { + while (chapters_count_ > 0) { + Chapter& chapter = chapters_[--chapters_count_]; + chapter.Clear(); + } + + delete[] chapters_; + chapters_ = NULL; +} + +int Chapters::Count() const { return chapters_count_; } + +Chapter* Chapters::AddChapter(unsigned int* seed) { + if (!ExpandChaptersArray()) + return NULL; + + Chapter& chapter = chapters_[chapters_count_++]; + chapter.Init(seed); + + return &chapter; +} + +bool Chapters::Write(IMkvWriter* writer) const { + if (writer == NULL) + return false; + + const uint64_t payload_size = WriteEdition(NULL); // return size only + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapters, payload_size)) + return false; + + const int64_t start = writer->Position(); + + if (WriteEdition(writer) == 0) // error + return false; + + const int64_t stop = writer->Position(); + + if (stop >= start && uint64_t(stop - start) != payload_size) + return false; + + return true; +} + +bool Chapters::ExpandChaptersArray() { + if (chapters_size_ > chapters_count_) + return true; // nothing to do yet + + const int size = (chapters_size_ == 0) ? 1 : 2 * chapters_size_; + + Chapter* const chapters = new (std::nothrow) Chapter[size]; // NOLINT + if (chapters == NULL) + return false; + + for (int idx = 0; idx < chapters_count_; ++idx) { + const Chapter& src = chapters_[idx]; + Chapter* const dst = chapters + idx; + src.ShallowCopy(dst); + } + + delete[] chapters_; + + chapters_ = chapters; + chapters_size_ = size; + + return true; +} + +uint64_t Chapters::WriteEdition(IMkvWriter* writer) const { + uint64_t payload_size = 0; + + for (int idx = 0; idx < chapters_count_; ++idx) { + const Chapter& chapter = chapters_[idx]; + payload_size += chapter.WriteAtom(NULL); + } + + const uint64_t edition_size = + EbmlMasterElementSize(libwebm::kMkvEditionEntry, payload_size) + + payload_size; + + if (writer == NULL) // return size only + return edition_size; + + const int64_t start = writer->Position(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvEditionEntry, payload_size)) + return 0; // error + + for (int idx = 0; idx < chapters_count_; ++idx) { + const Chapter& chapter = chapters_[idx]; + + const uint64_t chapter_size = chapter.WriteAtom(writer); + if (chapter_size == 0) // error + return 0; + } + + const int64_t stop = writer->Position(); + + if (stop >= start && uint64_t(stop - start) != edition_size) + return 0; + + return edition_size; +} + +// Tag Class + +bool Tag::add_simple_tag(const char* tag_name, const char* tag_string) { + if (!ExpandSimpleTagsArray()) + return false; + + SimpleTag& st = simple_tags_[simple_tags_count_++]; + st.Init(); + + if (!st.set_tag_name(tag_name)) + return false; + + if (!st.set_tag_string(tag_string)) + return false; + + return true; +} + +Tag::Tag() { + simple_tags_ = NULL; + simple_tags_size_ = 0; + simple_tags_count_ = 0; +} + +Tag::~Tag() {} + +void Tag::ShallowCopy(Tag* dst) const { + dst->simple_tags_ = simple_tags_; + dst->simple_tags_size_ = simple_tags_size_; + dst->simple_tags_count_ = simple_tags_count_; +} + +void Tag::Clear() { + while (simple_tags_count_ > 0) { + SimpleTag& st = simple_tags_[--simple_tags_count_]; + st.Clear(); + } + + delete[] simple_tags_; + simple_tags_ = NULL; + + simple_tags_size_ = 0; +} + +bool Tag::ExpandSimpleTagsArray() { + if (simple_tags_size_ > simple_tags_count_) + return true; // nothing to do yet + + const int size = (simple_tags_size_ == 0) ? 1 : 2 * simple_tags_size_; + + SimpleTag* const simple_tags = new (std::nothrow) SimpleTag[size]; // NOLINT + if (simple_tags == NULL) + return false; + + for (int idx = 0; idx < simple_tags_count_; ++idx) { + simple_tags[idx] = simple_tags_[idx]; // shallow copy + } + + delete[] simple_tags_; + + simple_tags_ = simple_tags; + simple_tags_size_ = size; + + return true; +} + +uint64_t Tag::Write(IMkvWriter* writer) const { + uint64_t payload_size = 0; + + for (int idx = 0; idx < simple_tags_count_; ++idx) { + const SimpleTag& st = simple_tags_[idx]; + payload_size += st.Write(NULL); + } + + const uint64_t tag_size = + EbmlMasterElementSize(libwebm::kMkvTag, payload_size) + payload_size; + + if (writer == NULL) + return tag_size; + + const int64_t start = writer->Position(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvTag, payload_size)) + return 0; + + for (int idx = 0; idx < simple_tags_count_; ++idx) { + const SimpleTag& st = simple_tags_[idx]; + + if (!st.Write(writer)) + return 0; + } + + const int64_t stop = writer->Position(); + + if (stop >= start && uint64_t(stop - start) != tag_size) + return 0; + + return tag_size; +} + +// Tag::SimpleTag + +void Tag::SimpleTag::Init() { + tag_name_ = NULL; + tag_string_ = NULL; +} + +void Tag::SimpleTag::Clear() { + StrCpy(NULL, &tag_name_); + StrCpy(NULL, &tag_string_); +} + +bool Tag::SimpleTag::set_tag_name(const char* tag_name) { + return StrCpy(tag_name, &tag_name_); +} + +bool Tag::SimpleTag::set_tag_string(const char* tag_string) { + return StrCpy(tag_string, &tag_string_); +} + +uint64_t Tag::SimpleTag::Write(IMkvWriter* writer) const { + uint64_t payload_size = EbmlElementSize(libwebm::kMkvTagName, tag_name_); + + payload_size += EbmlElementSize(libwebm::kMkvTagString, tag_string_); + + const uint64_t simple_tag_size = + EbmlMasterElementSize(libwebm::kMkvSimpleTag, payload_size) + + payload_size; + + if (writer == NULL) + return simple_tag_size; + + const int64_t start = writer->Position(); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvSimpleTag, payload_size)) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvTagName, tag_name_)) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvTagString, tag_string_)) + return 0; + + const int64_t stop = writer->Position(); + + if (stop >= start && uint64_t(stop - start) != simple_tag_size) + return 0; + + return simple_tag_size; +} + +// Tags Class + +Tags::Tags() : tags_size_(0), tags_count_(0), tags_(NULL) {} + +Tags::~Tags() { + while (tags_count_ > 0) { + Tag& tag = tags_[--tags_count_]; + tag.Clear(); + } + + delete[] tags_; + tags_ = NULL; +} + +int Tags::Count() const { return tags_count_; } + +Tag* Tags::AddTag() { + if (!ExpandTagsArray()) + return NULL; + + Tag& tag = tags_[tags_count_++]; + + return &tag; +} + +bool Tags::Write(IMkvWriter* writer) const { + if (writer == NULL) + return false; + + uint64_t payload_size = 0; + + for (int idx = 0; idx < tags_count_; ++idx) { + const Tag& tag = tags_[idx]; + payload_size += tag.Write(NULL); + } + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvTags, payload_size)) + return false; + + const int64_t start = writer->Position(); + + for (int idx = 0; idx < tags_count_; ++idx) { + const Tag& tag = tags_[idx]; + + const uint64_t tag_size = tag.Write(writer); + if (tag_size == 0) // error + return 0; + } + + const int64_t stop = writer->Position(); + + if (stop >= start && uint64_t(stop - start) != payload_size) + return false; + + return true; +} + +bool Tags::ExpandTagsArray() { + if (tags_size_ > tags_count_) + return true; // nothing to do yet + + const int size = (tags_size_ == 0) ? 1 : 2 * tags_size_; + + Tag* const tags = new (std::nothrow) Tag[size]; // NOLINT + if (tags == NULL) + return false; + + for (int idx = 0; idx < tags_count_; ++idx) { + const Tag& src = tags_[idx]; + Tag* const dst = tags + idx; + src.ShallowCopy(dst); + } + + delete[] tags_; + + tags_ = tags; + tags_size_ = size; + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// Cluster class + +Cluster::Cluster(uint64_t timecode, int64_t cues_pos, uint64_t timecode_scale, + bool write_last_frame_with_duration, bool fixed_size_timecode) + : blocks_added_(0), + finalized_(false), + fixed_size_timecode_(fixed_size_timecode), + header_written_(false), + payload_size_(0), + position_for_cues_(cues_pos), + size_position_(-1), + timecode_(timecode), + timecode_scale_(timecode_scale), + write_last_frame_with_duration_(write_last_frame_with_duration), + writer_(NULL) {} + +Cluster::~Cluster() { + // Delete any stored frames that are left behind. This will happen if the + // Cluster was not Finalized for whatever reason. + while (!stored_frames_.empty()) { + while (!stored_frames_.begin()->second.empty()) { + delete stored_frames_.begin()->second.front(); + stored_frames_.begin()->second.pop_front(); + } + stored_frames_.erase(stored_frames_.begin()->first); + } +} + +bool Cluster::Init(IMkvWriter* ptr_writer) { + if (!ptr_writer) { + return false; + } + writer_ = ptr_writer; + return true; +} + +bool Cluster::AddFrame(const Frame* const frame) { + return QueueOrWriteFrame(frame); +} + +bool Cluster::AddFrame(const uint8_t* data, uint64_t length, + uint64_t track_number, uint64_t abs_timecode, + bool is_key) { + Frame frame; + if (!frame.Init(data, length)) + return false; + frame.set_track_number(track_number); + frame.set_timestamp(abs_timecode); + frame.set_is_key(is_key); + return QueueOrWriteFrame(&frame); +} + +bool Cluster::AddFrameWithAdditional(const uint8_t* data, uint64_t length, + const uint8_t* additional, + uint64_t additional_length, + uint64_t add_id, uint64_t track_number, + uint64_t abs_timecode, bool is_key) { + if (!additional || additional_length == 0) { + return false; + } + Frame frame; + if (!frame.Init(data, length) || + !frame.AddAdditionalData(additional, additional_length, add_id)) { + return false; + } + frame.set_track_number(track_number); + frame.set_timestamp(abs_timecode); + frame.set_is_key(is_key); + return QueueOrWriteFrame(&frame); +} + +bool Cluster::AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length, + int64_t discard_padding, + uint64_t track_number, + uint64_t abs_timecode, bool is_key) { + Frame frame; + if (!frame.Init(data, length)) + return false; + frame.set_discard_padding(discard_padding); + frame.set_track_number(track_number); + frame.set_timestamp(abs_timecode); + frame.set_is_key(is_key); + return QueueOrWriteFrame(&frame); +} + +bool Cluster::AddMetadata(const uint8_t* data, uint64_t length, + uint64_t track_number, uint64_t abs_timecode, + uint64_t duration_timecode) { + Frame frame; + if (!frame.Init(data, length)) + return false; + frame.set_track_number(track_number); + frame.set_timestamp(abs_timecode); + frame.set_duration(duration_timecode); + frame.set_is_key(true); // All metadata blocks are keyframes. + return QueueOrWriteFrame(&frame); +} + +void Cluster::AddPayloadSize(uint64_t size) { payload_size_ += size; } + +bool Cluster::Finalize() { + return !write_last_frame_with_duration_ && Finalize(false, 0); +} + +bool Cluster::Finalize(bool set_last_frame_duration, uint64_t duration) { + if (!writer_ || finalized_) + return false; + + if (write_last_frame_with_duration_) { + // Write out held back Frames. This essentially performs a k-way merge + // across all tracks in the increasing order of timestamps. + while (!stored_frames_.empty()) { + Frame* frame = stored_frames_.begin()->second.front(); + + // Get the next frame to write (frame with least timestamp across all + // tracks). + for (FrameMapIterator frames_iterator = ++stored_frames_.begin(); + frames_iterator != stored_frames_.end(); ++frames_iterator) { + if (frames_iterator->second.front()->timestamp() < frame->timestamp()) { + frame = frames_iterator->second.front(); + } + } + + // Set the duration if it's the last frame for the track. + if (set_last_frame_duration && + stored_frames_[frame->track_number()].size() == 1 && + !frame->duration_set()) { + frame->set_duration(duration - frame->timestamp()); + if (!frame->is_key() && !frame->reference_block_timestamp_set()) { + frame->set_reference_block_timestamp( + last_block_timestamp_[frame->track_number()]); + } + } + + // Write the frame and remove it from |stored_frames_|. + const bool wrote_frame = DoWriteFrame(frame); + stored_frames_[frame->track_number()].pop_front(); + if (stored_frames_[frame->track_number()].empty()) { + stored_frames_.erase(frame->track_number()); + } + delete frame; + if (!wrote_frame) + return false; + } + } + + if (size_position_ == -1) + return false; + + if (writer_->Seekable()) { + const int64_t pos = writer_->Position(); + + if (writer_->Position(size_position_)) + return false; + + if (WriteUIntSize(writer_, payload_size(), 8)) + return false; + + if (writer_->Position(pos)) + return false; + } + + finalized_ = true; + + return true; +} + +uint64_t Cluster::Size() const { + const uint64_t element_size = + EbmlMasterElementSize(libwebm::kMkvCluster, 0xFFFFFFFFFFFFFFFFULL) + + payload_size_; + return element_size; +} + +bool Cluster::PreWriteBlock() { + if (finalized_) + return false; + + if (!header_written_) { + if (!WriteClusterHeader()) + return false; + } + + return true; +} + +void Cluster::PostWriteBlock(uint64_t element_size) { + AddPayloadSize(element_size); + ++blocks_added_; +} + +int64_t Cluster::GetRelativeTimecode(int64_t abs_timecode) const { + const int64_t cluster_timecode = this->Cluster::timecode(); + const int64_t rel_timecode = + static_cast<int64_t>(abs_timecode) - cluster_timecode; + + if (rel_timecode < 0 || rel_timecode > kMaxBlockTimecode) + return -1; + + return rel_timecode; +} + +bool Cluster::DoWriteFrame(const Frame* const frame) { + if (!frame || !frame->IsValid()) + return false; + + if (!PreWriteBlock()) + return false; + + const uint64_t element_size = WriteFrame(writer_, frame, this); + if (element_size == 0) + return false; + + PostWriteBlock(element_size); + last_block_timestamp_[frame->track_number()] = frame->timestamp(); + return true; +} + +bool Cluster::QueueOrWriteFrame(const Frame* const frame) { + if (!frame || !frame->IsValid()) + return false; + + // If |write_last_frame_with_duration_| is not set, then write the frame right + // away. + if (!write_last_frame_with_duration_) { + return DoWriteFrame(frame); + } + + // Queue the current frame. + uint64_t track_number = frame->track_number(); + Frame* const frame_to_store = new Frame(); + frame_to_store->CopyFrom(*frame); + stored_frames_[track_number].push_back(frame_to_store); + + // Iterate through all queued frames in the current track except the last one + // and write it if it is okay to do so (i.e.) no other track has an held back + // frame with timestamp <= the timestamp of the frame in question. + std::vector<std::list<Frame*>::iterator> frames_to_erase; + for (std::list<Frame*>::iterator + current_track_iterator = stored_frames_[track_number].begin(), + end = --stored_frames_[track_number].end(); + current_track_iterator != end; ++current_track_iterator) { + const Frame* const frame_to_write = *current_track_iterator; + bool okay_to_write = true; + for (FrameMapIterator track_iterator = stored_frames_.begin(); + track_iterator != stored_frames_.end(); ++track_iterator) { + if (track_iterator->first == track_number) { + continue; + } + if (track_iterator->second.front()->timestamp() < + frame_to_write->timestamp()) { + okay_to_write = false; + break; + } + } + if (okay_to_write) { + const bool wrote_frame = DoWriteFrame(frame_to_write); + delete frame_to_write; + if (!wrote_frame) + return false; + frames_to_erase.push_back(current_track_iterator); + } else { + break; + } + } + for (std::vector<std::list<Frame*>::iterator>::iterator iterator = + frames_to_erase.begin(); + iterator != frames_to_erase.end(); ++iterator) { + stored_frames_[track_number].erase(*iterator); + } + return true; +} + +bool Cluster::WriteClusterHeader() { + if (finalized_) + return false; + + if (WriteID(writer_, libwebm::kMkvCluster)) + return false; + + // Save for later. + size_position_ = writer_->Position(); + + // Write "unknown" (EBML coded -1) as cluster size value. We need to write 8 + // bytes because we do not know how big our cluster will be. + if (SerializeInt(writer_, kEbmlUnknownValue, 8)) + return false; + + if (!WriteEbmlElement(writer_, libwebm::kMkvTimecode, timecode(), + fixed_size_timecode_ ? 8 : 0)) { + return false; + } + AddPayloadSize(EbmlElementSize(libwebm::kMkvTimecode, timecode(), + fixed_size_timecode_ ? 8 : 0)); + header_written_ = true; + + return true; +} + +/////////////////////////////////////////////////////////////// +// +// SeekHead Class + +SeekHead::SeekHead() : start_pos_(0ULL) { + for (int32_t i = 0; i < kSeekEntryCount; ++i) { + seek_entry_id_[i] = 0; + seek_entry_pos_[i] = 0; + } +} + +SeekHead::~SeekHead() {} + +bool SeekHead::Finalize(IMkvWriter* writer) const { + if (writer->Seekable()) { + if (start_pos_ == -1) + return false; + + uint64_t payload_size = 0; + uint64_t entry_size[kSeekEntryCount]; + + for (int32_t i = 0; i < kSeekEntryCount; ++i) { + if (seek_entry_id_[i] != 0) { + entry_size[i] = EbmlElementSize(libwebm::kMkvSeekID, + static_cast<uint64>(seek_entry_id_[i])); + entry_size[i] += EbmlElementSize( + libwebm::kMkvSeekPosition, static_cast<uint64>(seek_entry_pos_[i])); + + payload_size += + EbmlMasterElementSize(libwebm::kMkvSeek, entry_size[i]) + + entry_size[i]; + } + } + + // No SeekHead elements + if (payload_size == 0) + return true; + + const int64_t pos = writer->Position(); + if (writer->Position(start_pos_)) + return false; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeekHead, payload_size)) + return false; + + for (int32_t i = 0; i < kSeekEntryCount; ++i) { + if (seek_entry_id_[i] != 0) { + if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeek, entry_size[i])) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvSeekID, + static_cast<uint64>(seek_entry_id_[i]))) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvSeekPosition, + static_cast<uint64>(seek_entry_pos_[i]))) + return false; + } + } + + const uint64_t total_entry_size = kSeekEntryCount * MaxEntrySize(); + const uint64_t total_size = + EbmlMasterElementSize(libwebm::kMkvSeekHead, total_entry_size) + + total_entry_size; + const int64_t size_left = total_size - (writer->Position() - start_pos_); + + const uint64_t bytes_written = WriteVoidElement(writer, size_left); + if (!bytes_written) + return false; + + if (writer->Position(pos)) + return false; + } + + return true; +} + +bool SeekHead::Write(IMkvWriter* writer) { + const uint64_t entry_size = kSeekEntryCount * MaxEntrySize(); + const uint64_t size = + EbmlMasterElementSize(libwebm::kMkvSeekHead, entry_size); + + start_pos_ = writer->Position(); + + const uint64_t bytes_written = WriteVoidElement(writer, size + entry_size); + if (!bytes_written) + return false; + + return true; +} + +bool SeekHead::AddSeekEntry(uint32_t id, uint64_t pos) { + for (int32_t i = 0; i < kSeekEntryCount; ++i) { + if (seek_entry_id_[i] == 0) { + seek_entry_id_[i] = id; + seek_entry_pos_[i] = pos; + return true; + } + } + return false; +} + +uint32_t SeekHead::GetId(int index) const { + if (index < 0 || index >= kSeekEntryCount) + return UINT_MAX; + return seek_entry_id_[index]; +} + +uint64_t SeekHead::GetPosition(int index) const { + if (index < 0 || index >= kSeekEntryCount) + return ULLONG_MAX; + return seek_entry_pos_[index]; +} + +bool SeekHead::SetSeekEntry(int index, uint32_t id, uint64_t position) { + if (index < 0 || index >= kSeekEntryCount) + return false; + seek_entry_id_[index] = id; + seek_entry_pos_[index] = position; + return true; +} + +uint64_t SeekHead::MaxEntrySize() const { + const uint64_t max_entry_payload_size = + EbmlElementSize(libwebm::kMkvSeekID, + static_cast<uint64>(UINT64_C(0xffffffff))) + + EbmlElementSize(libwebm::kMkvSeekPosition, + static_cast<uint64>(UINT64_C(0xffffffffffffffff))); + const uint64_t max_entry_size = + EbmlMasterElementSize(libwebm::kMkvSeek, max_entry_payload_size) + + max_entry_payload_size; + + return max_entry_size; +} + +/////////////////////////////////////////////////////////////// +// +// SegmentInfo Class + +SegmentInfo::SegmentInfo() + : duration_(-1.0), + muxing_app_(NULL), + timecode_scale_(1000000ULL), + writing_app_(NULL), + date_utc_(LLONG_MIN), + duration_pos_(-1) {} + +SegmentInfo::~SegmentInfo() { + delete[] muxing_app_; + delete[] writing_app_; +} + +bool SegmentInfo::Init() { + int32_t major; + int32_t minor; + int32_t build; + int32_t revision; + GetVersion(&major, &minor, &build, &revision); + char temp[256]; +#ifdef _MSC_VER + sprintf_s(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major, + minor, build, revision); +#else + snprintf(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major, + minor, build, revision); +#endif + + const size_t app_len = strlen(temp) + 1; + + delete[] muxing_app_; + + muxing_app_ = new (std::nothrow) char[app_len]; // NOLINT + if (!muxing_app_) + return false; + +#ifdef _MSC_VER + strcpy_s(muxing_app_, app_len, temp); +#else + strcpy(muxing_app_, temp); +#endif + + set_writing_app(temp); + if (!writing_app_) + return false; + return true; +} + +bool SegmentInfo::Finalize(IMkvWriter* writer) const { + if (!writer) + return false; + + if (duration_ > 0.0) { + if (writer->Seekable()) { + if (duration_pos_ == -1) + return false; + + const int64_t pos = writer->Position(); + + if (writer->Position(duration_pos_)) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvDuration, + static_cast<float>(duration_))) + return false; + + if (writer->Position(pos)) + return false; + } + } + + return true; +} + +bool SegmentInfo::Write(IMkvWriter* writer) { + if (!writer || !muxing_app_ || !writing_app_) + return false; + + uint64_t size = EbmlElementSize(libwebm::kMkvTimecodeScale, + static_cast<uint64>(timecode_scale_)); + if (duration_ > 0.0) + size += + EbmlElementSize(libwebm::kMkvDuration, static_cast<float>(duration_)); + if (date_utc_ != LLONG_MIN) + size += EbmlDateElementSize(libwebm::kMkvDateUTC); + size += EbmlElementSize(libwebm::kMkvMuxingApp, muxing_app_); + size += EbmlElementSize(libwebm::kMkvWritingApp, writing_app_); + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvInfo, size)) + return false; + + const int64_t payload_position = writer->Position(); + if (payload_position < 0) + return false; + + if (!WriteEbmlElement(writer, libwebm::kMkvTimecodeScale, + static_cast<uint64>(timecode_scale_))) + return false; + + if (duration_ > 0.0) { + // Save for later + duration_pos_ = writer->Position(); + + if (!WriteEbmlElement(writer, libwebm::kMkvDuration, + static_cast<float>(duration_))) + return false; + } + + if (date_utc_ != LLONG_MIN) + WriteEbmlDateElement(writer, libwebm::kMkvDateUTC, date_utc_); + + if (!WriteEbmlElement(writer, libwebm::kMkvMuxingApp, muxing_app_)) + return false; + if (!WriteEbmlElement(writer, libwebm::kMkvWritingApp, writing_app_)) + return false; + + const int64_t stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64_t>(size)) + return false; + + return true; +} + +void SegmentInfo::set_muxing_app(const char* app) { + if (app) { + const size_t length = strlen(app) + 1; + char* temp_str = new (std::nothrow) char[length]; // NOLINT + if (!temp_str) + return; + +#ifdef _MSC_VER + strcpy_s(temp_str, length, app); +#else + strcpy(temp_str, app); +#endif + + delete[] muxing_app_; + muxing_app_ = temp_str; + } +} + +void SegmentInfo::set_writing_app(const char* app) { + if (app) { + const size_t length = strlen(app) + 1; + char* temp_str = new (std::nothrow) char[length]; // NOLINT + if (!temp_str) + return; + +#ifdef _MSC_VER + strcpy_s(temp_str, length, app); +#else + strcpy(temp_str, app); +#endif + + delete[] writing_app_; + writing_app_ = temp_str; + } +} + +/////////////////////////////////////////////////////////////// +// +// Segment Class + +Segment::Segment() + : chunk_count_(0), + chunk_name_(NULL), + chunk_writer_cluster_(NULL), + chunk_writer_cues_(NULL), + chunk_writer_header_(NULL), + chunking_(false), + chunking_base_name_(NULL), + cluster_list_(NULL), + cluster_list_capacity_(0), + cluster_list_size_(0), + cues_position_(kAfterClusters), + cues_track_(0), + force_new_cluster_(false), + frames_(NULL), + frames_capacity_(0), + frames_size_(0), + has_video_(false), + header_written_(false), + last_block_duration_(0), + last_timestamp_(0), + max_cluster_duration_(kDefaultMaxClusterDuration), + max_cluster_size_(0), + mode_(kFile), + new_cuepoint_(false), + output_cues_(true), + accurate_cluster_duration_(false), + fixed_size_cluster_timecode_(false), + estimate_file_duration_(false), + payload_pos_(0), + size_position_(0), + doc_type_version_(kDefaultDocTypeVersion), + doc_type_version_written_(0), + duration_(0.0), + writer_cluster_(NULL), + writer_cues_(NULL), + writer_header_(NULL) { + const time_t curr_time = time(NULL); + seed_ = static_cast<unsigned int>(curr_time); +#ifdef _WIN32 + srand(seed_); +#endif +} + +Segment::~Segment() { + if (cluster_list_) { + for (int32_t i = 0; i < cluster_list_size_; ++i) { + Cluster* const cluster = cluster_list_[i]; + delete cluster; + } + delete[] cluster_list_; + } + + if (frames_) { + for (int32_t i = 0; i < frames_size_; ++i) { + Frame* const frame = frames_[i]; + delete frame; + } + delete[] frames_; + } + + delete[] chunk_name_; + delete[] chunking_base_name_; + + if (chunk_writer_cluster_) { + chunk_writer_cluster_->Close(); + delete chunk_writer_cluster_; + } + if (chunk_writer_cues_) { + chunk_writer_cues_->Close(); + delete chunk_writer_cues_; + } + if (chunk_writer_header_) { + chunk_writer_header_->Close(); + delete chunk_writer_header_; + } +} + +void Segment::MoveCuesBeforeClustersHelper(uint64_t diff, int32_t index, + uint64_t* cues_size) { + CuePoint* const cue_point = cues_.GetCueByIndex(index); + if (cue_point == NULL) + return; + const uint64_t old_cue_point_size = cue_point->Size(); + const uint64_t cluster_pos = cue_point->cluster_pos() + diff; + cue_point->set_cluster_pos(cluster_pos); // update the new cluster position + // New size of the cue is computed as follows + // Let a = current sum of size of all CuePoints + // Let b = Increase in Cue Point's size due to this iteration + // Let c = Increase in size of Cues Element's length due to this iteration + // (This is computed as CodedSize(a + b) - CodedSize(a)) + // Let d = b + c. Now d is the |diff| passed to the next recursive call. + // Let e = a + b. Now e is the |cues_size| passed to the next recursive + // call. + const uint64_t cue_point_size_diff = cue_point->Size() - old_cue_point_size; + const uint64_t cue_size_diff = + GetCodedUIntSize(*cues_size + cue_point_size_diff) - + GetCodedUIntSize(*cues_size); + *cues_size += cue_point_size_diff; + diff = cue_size_diff + cue_point_size_diff; + if (diff > 0) { + for (int32_t i = 0; i < cues_.cue_entries_size(); ++i) { + MoveCuesBeforeClustersHelper(diff, i, cues_size); + } + } +} + +void Segment::MoveCuesBeforeClusters() { + const uint64_t current_cue_size = cues_.Size(); + uint64_t cue_size = 0; + for (int32_t i = 0; i < cues_.cue_entries_size(); ++i) + cue_size += cues_.GetCueByIndex(i)->Size(); + for (int32_t i = 0; i < cues_.cue_entries_size(); ++i) + MoveCuesBeforeClustersHelper(current_cue_size, i, &cue_size); + + // Adjust the Seek Entry to reflect the change in position + // of Cluster and Cues + int32_t cluster_index = 0; + int32_t cues_index = 0; + for (int32_t i = 0; i < SeekHead::kSeekEntryCount; ++i) { + if (seek_head_.GetId(i) == libwebm::kMkvCluster) + cluster_index = i; + if (seek_head_.GetId(i) == libwebm::kMkvCues) + cues_index = i; + } + seek_head_.SetSeekEntry(cues_index, libwebm::kMkvCues, + seek_head_.GetPosition(cluster_index)); + seek_head_.SetSeekEntry(cluster_index, libwebm::kMkvCluster, + cues_.Size() + seek_head_.GetPosition(cues_index)); +} + +bool Segment::Init(IMkvWriter* ptr_writer) { + if (!ptr_writer) { + return false; + } + writer_cluster_ = ptr_writer; + writer_cues_ = ptr_writer; + writer_header_ = ptr_writer; + memset(&track_frames_written_, 0, + sizeof(track_frames_written_[0]) * kMaxTrackNumber); + memset(&last_track_timestamp_, 0, + sizeof(last_track_timestamp_[0]) * kMaxTrackNumber); + return segment_info_.Init(); +} + +bool Segment::CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader, + IMkvWriter* writer) { + if (!writer->Seekable() || chunking_) + return false; + const int64_t cluster_offset = + cluster_list_[0]->size_position() - GetUIntSize(libwebm::kMkvCluster); + + // Copy the headers. + if (!ChunkedCopy(reader, writer, 0, cluster_offset)) + return false; + + // Recompute cue positions and seek entries. + MoveCuesBeforeClusters(); + + // Write cues and seek entries. + // TODO(vigneshv): As of now, it's safe to call seek_head_.Finalize() for the + // second time with a different writer object. But the name Finalize() doesn't + // indicate something we want to call more than once. So consider renaming it + // to write() or some such. + if (!cues_.Write(writer) || !seek_head_.Finalize(writer)) + return false; + + // Copy the Clusters. + if (!ChunkedCopy(reader, writer, cluster_offset, + cluster_end_offset_ - cluster_offset)) + return false; + + // Update the Segment size in case the Cues size has changed. + const int64_t pos = writer->Position(); + const int64_t segment_size = writer->Position() - payload_pos_; + if (writer->Position(size_position_) || + WriteUIntSize(writer, segment_size, 8) || writer->Position(pos)) + return false; + return true; +} + +bool Segment::Finalize() { + if (WriteFramesAll() < 0) + return false; + + // In kLive mode, call Cluster::Finalize only if |accurate_cluster_duration_| + // is set. In all other modes, always call Cluster::Finalize. + if ((mode_ == kLive ? accurate_cluster_duration_ : true) && + cluster_list_size_ > 0) { + // Update last cluster's size + Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; + + // For the last frame of the last Cluster, we don't write it as a BlockGroup + // with Duration unless the frame itself has duration set explicitly. + if (!old_cluster || !old_cluster->Finalize(false, 0)) + return false; + } + + if (mode_ == kFile) { + if (chunking_ && chunk_writer_cluster_) { + chunk_writer_cluster_->Close(); + chunk_count_++; + } + + double duration = + (static_cast<double>(last_timestamp_) + last_block_duration_) / + segment_info_.timecode_scale(); + if (duration_ > 0.0) { + duration = duration_; + } else { + if (last_block_duration_ == 0 && estimate_file_duration_) { + const int num_tracks = static_cast<int>(tracks_.track_entries_size()); + for (int i = 0; i < num_tracks; ++i) { + if (track_frames_written_[i] < 2) + continue; + + // Estimate the duration for the last block of a Track. + const double nano_per_frame = + static_cast<double>(last_track_timestamp_[i]) / + (track_frames_written_[i] - 1); + const double track_duration = + (last_track_timestamp_[i] + nano_per_frame) / + segment_info_.timecode_scale(); + if (track_duration > duration) + duration = track_duration; + } + } + } + segment_info_.set_duration(duration); + if (!segment_info_.Finalize(writer_header_)) + return false; + + if (output_cues_) + if (!seek_head_.AddSeekEntry(libwebm::kMkvCues, MaxOffset())) + return false; + + if (chunking_) { + if (!chunk_writer_cues_) + return false; + + char* name = NULL; + if (!UpdateChunkName("cues", &name)) + return false; + + const bool cues_open = chunk_writer_cues_->Open(name); + delete[] name; + if (!cues_open) + return false; + } + + cluster_end_offset_ = writer_cluster_->Position(); + + // Write the seek headers and cues + if (output_cues_) + if (!cues_.Write(writer_cues_)) + return false; + + if (!seek_head_.Finalize(writer_header_)) + return false; + + if (writer_header_->Seekable()) { + if (size_position_ == -1) + return false; + + const int64_t segment_size = MaxOffset(); + if (segment_size < 1) + return false; + + const int64_t pos = writer_header_->Position(); + UpdateDocTypeVersion(); + if (doc_type_version_ != doc_type_version_written_) { + if (writer_header_->Position(0)) + return false; + + const char* const doc_type = + DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska; + if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type)) + return false; + if (writer_header_->Position() != ebml_header_size_) + return false; + + doc_type_version_written_ = doc_type_version_; + } + + if (writer_header_->Position(size_position_)) + return false; + + if (WriteUIntSize(writer_header_, segment_size, 8)) + return false; + + if (writer_header_->Position(pos)) + return false; + } + + if (chunking_) { + // Do not close any writers until the segment size has been written, + // otherwise the size may be off. + if (!chunk_writer_cues_ || !chunk_writer_header_) + return false; + + chunk_writer_cues_->Close(); + chunk_writer_header_->Close(); + } + } + + return true; +} + +Track* Segment::AddTrack(int32_t number) { + Track* const track = new (std::nothrow) Track(&seed_); // NOLINT + + if (!track) + return NULL; + + if (!tracks_.AddTrack(track, number)) { + delete track; + return NULL; + } + + return track; +} + +Chapter* Segment::AddChapter() { return chapters_.AddChapter(&seed_); } + +Tag* Segment::AddTag() { return tags_.AddTag(); } + +uint64_t Segment::AddVideoTrack(int32_t width, int32_t height, int32_t number) { + VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_); // NOLINT + if (!track) + return 0; + + track->set_type(Tracks::kVideo); + track->set_codec_id(Tracks::kVp8CodecId); + track->set_width(width); + track->set_height(height); + + if (!tracks_.AddTrack(track, number)) { + delete track; + return 0; + } + has_video_ = true; + + return track->number(); +} + +bool Segment::AddCuePoint(uint64_t timestamp, uint64_t track) { + if (cluster_list_size_ < 1) + return false; + + const Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; + if (!cluster) + return false; + + CuePoint* const cue = new (std::nothrow) CuePoint(); // NOLINT + if (!cue) + return false; + + cue->set_time(timestamp / segment_info_.timecode_scale()); + cue->set_block_number(cluster->blocks_added()); + cue->set_cluster_pos(cluster->position_for_cues()); + cue->set_track(track); + if (!cues_.AddCue(cue)) { + delete cue; + return false; + } + + new_cuepoint_ = false; + return true; +} + +uint64_t Segment::AddAudioTrack(int32_t sample_rate, int32_t channels, + int32_t number) { + AudioTrack* const track = new (std::nothrow) AudioTrack(&seed_); // NOLINT + if (!track) + return 0; + + track->set_type(Tracks::kAudio); + track->set_codec_id(Tracks::kVorbisCodecId); + track->set_sample_rate(sample_rate); + track->set_channels(channels); + + if (!tracks_.AddTrack(track, number)) { + delete track; + return 0; + } + + return track->number(); +} + +bool Segment::AddFrame(const uint8_t* data, uint64_t length, + uint64_t track_number, uint64_t timestamp, bool is_key) { + if (!data) + return false; + + Frame frame; + if (!frame.Init(data, length)) + return false; + frame.set_track_number(track_number); + frame.set_timestamp(timestamp); + frame.set_is_key(is_key); + return AddGenericFrame(&frame); +} + +bool Segment::AddFrameWithAdditional(const uint8_t* data, uint64_t length, + const uint8_t* additional, + uint64_t additional_length, + uint64_t add_id, uint64_t track_number, + uint64_t timestamp, bool is_key) { + if (!data || !additional) + return false; + + Frame frame; + if (!frame.Init(data, length) || + !frame.AddAdditionalData(additional, additional_length, add_id)) { + return false; + } + frame.set_track_number(track_number); + frame.set_timestamp(timestamp); + frame.set_is_key(is_key); + return AddGenericFrame(&frame); +} + +bool Segment::AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length, + int64_t discard_padding, + uint64_t track_number, + uint64_t timestamp, bool is_key) { + if (!data) + return false; + + Frame frame; + if (!frame.Init(data, length)) + return false; + frame.set_discard_padding(discard_padding); + frame.set_track_number(track_number); + frame.set_timestamp(timestamp); + frame.set_is_key(is_key); + return AddGenericFrame(&frame); +} + +bool Segment::AddMetadata(const uint8_t* data, uint64_t length, + uint64_t track_number, uint64_t timestamp_ns, + uint64_t duration_ns) { + if (!data) + return false; + + Frame frame; + if (!frame.Init(data, length)) + return false; + frame.set_track_number(track_number); + frame.set_timestamp(timestamp_ns); + frame.set_duration(duration_ns); + frame.set_is_key(true); // All metadata blocks are keyframes. + return AddGenericFrame(&frame); +} + +bool Segment::AddGenericFrame(const Frame* frame) { + if (!frame) + return false; + + if (!CheckHeaderInfo()) + return false; + + // Check for non-monotonically increasing timestamps. + if (frame->timestamp() < last_timestamp_) + return false; + + // Check if the track number is valid. + if (!tracks_.GetTrackByNumber(frame->track_number())) + return false; + + if (frame->discard_padding() != 0) + doc_type_version_ = 4; + + if (cluster_list_size_ > 0) { + const uint64_t timecode_scale = segment_info_.timecode_scale(); + const uint64_t frame_timecode = frame->timestamp() / timecode_scale; + + const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1]; + const uint64_t last_cluster_timecode = last_cluster->timecode(); + + const uint64_t rel_timecode = frame_timecode - last_cluster_timecode; + if (rel_timecode > kMaxBlockTimecode) { + force_new_cluster_ = true; + } + } + + // If the segment has a video track hold onto audio frames to make sure the + // audio that is associated with the start time of a video key-frame is + // muxed into the same cluster. + if (has_video_ && tracks_.TrackIsAudio(frame->track_number()) && + !force_new_cluster_) { + Frame* const new_frame = new (std::nothrow) Frame(); + if (!new_frame || !new_frame->CopyFrom(*frame)) { + delete new_frame; + return false; + } + if (!QueueFrame(new_frame)) { + delete new_frame; + return false; + } + track_frames_written_[frame->track_number() - 1]++; + return true; + } + + if (!DoNewClusterProcessing(frame->track_number(), frame->timestamp(), + frame->is_key())) { + return false; + } + + if (cluster_list_size_ < 1) + return false; + + Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; + if (!cluster) + return false; + + // If the Frame is not a SimpleBlock, then set the reference_block_timestamp + // if it is not set already. + bool frame_created = false; + if (!frame->CanBeSimpleBlock() && !frame->is_key() && + !frame->reference_block_timestamp_set()) { + Frame* const new_frame = new (std::nothrow) Frame(); + if (!new_frame || !new_frame->CopyFrom(*frame)) { + delete new_frame; + return false; + } + new_frame->set_reference_block_timestamp( + last_track_timestamp_[frame->track_number() - 1]); + frame = new_frame; + frame_created = true; + } + + if (!cluster->AddFrame(frame)) + return false; + + if (new_cuepoint_ && cues_track_ == frame->track_number()) { + if (!AddCuePoint(frame->timestamp(), cues_track_)) + return false; + } + + last_timestamp_ = frame->timestamp(); + last_track_timestamp_[frame->track_number() - 1] = frame->timestamp(); + last_block_duration_ = frame->duration(); + track_frames_written_[frame->track_number() - 1]++; + + if (frame_created) + delete frame; + return true; +} + +void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; } + +void Segment::AccurateClusterDuration(bool accurate_cluster_duration) { + accurate_cluster_duration_ = accurate_cluster_duration; +} + +void Segment::UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode) { + fixed_size_cluster_timecode_ = fixed_size_cluster_timecode; +} + +bool Segment::SetChunking(bool chunking, const char* filename) { + if (chunk_count_ > 0) + return false; + + if (chunking) { + if (!filename) + return false; + + // Check if we are being set to what is already set. + if (chunking_ && !strcmp(filename, chunking_base_name_)) + return true; + + const size_t name_length = strlen(filename) + 1; + char* const temp = new (std::nothrow) char[name_length]; // NOLINT + if (!temp) + return false; + +#ifdef _MSC_VER + strcpy_s(temp, name_length, filename); +#else + strcpy(temp, filename); +#endif + + delete[] chunking_base_name_; + chunking_base_name_ = temp; + + if (!UpdateChunkName("chk", &chunk_name_)) + return false; + + if (!chunk_writer_cluster_) { + chunk_writer_cluster_ = new (std::nothrow) MkvWriter(); // NOLINT + if (!chunk_writer_cluster_) + return false; + } + + if (!chunk_writer_cues_) { + chunk_writer_cues_ = new (std::nothrow) MkvWriter(); // NOLINT + if (!chunk_writer_cues_) + return false; + } + + if (!chunk_writer_header_) { + chunk_writer_header_ = new (std::nothrow) MkvWriter(); // NOLINT + if (!chunk_writer_header_) + return false; + } + + if (!chunk_writer_cluster_->Open(chunk_name_)) + return false; + + const size_t header_length = strlen(filename) + strlen(".hdr") + 1; + char* const header = new (std::nothrow) char[header_length]; // NOLINT + if (!header) + return false; + +#ifdef _MSC_VER + strcpy_s(header, header_length - strlen(".hdr"), chunking_base_name_); + strcat_s(header, header_length, ".hdr"); +#else + strcpy(header, chunking_base_name_); + strcat(header, ".hdr"); +#endif + if (!chunk_writer_header_->Open(header)) { + delete[] header; + return false; + } + + writer_cluster_ = chunk_writer_cluster_; + writer_cues_ = chunk_writer_cues_; + writer_header_ = chunk_writer_header_; + + delete[] header; + } + + chunking_ = chunking; + + return true; +} + +bool Segment::CuesTrack(uint64_t track_number) { + const Track* const track = GetTrackByNumber(track_number); + if (!track) + return false; + + cues_track_ = track_number; + return true; +} + +void Segment::ForceNewClusterOnNextFrame() { force_new_cluster_ = true; } + +Track* Segment::GetTrackByNumber(uint64_t track_number) const { + return tracks_.GetTrackByNumber(track_number); +} + +bool Segment::WriteSegmentHeader() { + UpdateDocTypeVersion(); + + const char* const doc_type = + DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska; + if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type)) + return false; + doc_type_version_written_ = doc_type_version_; + ebml_header_size_ = static_cast<int32_t>(writer_header_->Position()); + + // Write "unknown" (-1) as segment size value. If mode is kFile, Segment + // will write over duration when the file is finalized. + if (WriteID(writer_header_, libwebm::kMkvSegment)) + return false; + + // Save for later. + size_position_ = writer_header_->Position(); + + // Write "unknown" (EBML coded -1) as segment size value. We need to write 8 + // bytes because if we are going to overwrite the segment size later we do + // not know how big our segment will be. + if (SerializeInt(writer_header_, kEbmlUnknownValue, 8)) + return false; + + payload_pos_ = writer_header_->Position(); + + if (mode_ == kFile && writer_header_->Seekable()) { + // Set the duration > 0.0 so SegmentInfo will write out the duration. When + // the muxer is done writing we will set the correct duration and have + // SegmentInfo upadte it. + segment_info_.set_duration(1.0); + + if (!seek_head_.Write(writer_header_)) + return false; + } + + if (!seek_head_.AddSeekEntry(libwebm::kMkvInfo, MaxOffset())) + return false; + if (!segment_info_.Write(writer_header_)) + return false; + + if (!seek_head_.AddSeekEntry(libwebm::kMkvTracks, MaxOffset())) + return false; + if (!tracks_.Write(writer_header_)) + return false; + + if (chapters_.Count() > 0) { + if (!seek_head_.AddSeekEntry(libwebm::kMkvChapters, MaxOffset())) + return false; + if (!chapters_.Write(writer_header_)) + return false; + } + + if (tags_.Count() > 0) { + if (!seek_head_.AddSeekEntry(libwebm::kMkvTags, MaxOffset())) + return false; + if (!tags_.Write(writer_header_)) + return false; + } + + if (chunking_ && (mode_ == kLive || !writer_header_->Seekable())) { + if (!chunk_writer_header_) + return false; + + chunk_writer_header_->Close(); + } + + header_written_ = true; + + return true; +} + +// Here we are testing whether to create a new cluster, given a frame +// having time frame_timestamp_ns. +// +int Segment::TestFrame(uint64_t track_number, uint64_t frame_timestamp_ns, + bool is_key) const { + if (force_new_cluster_) + return 1; + + // If no clusters have been created yet, then create a new cluster + // and write this frame immediately, in the new cluster. This path + // should only be followed once, the first time we attempt to write + // a frame. + + if (cluster_list_size_ <= 0) + return 1; + + // There exists at least one cluster. We must compare the frame to + // the last cluster, in order to determine whether the frame is + // written to the existing cluster, or that a new cluster should be + // created. + + const uint64_t timecode_scale = segment_info_.timecode_scale(); + const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale; + + const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1]; + const uint64_t last_cluster_timecode = last_cluster->timecode(); + + // For completeness we test for the case when the frame's timecode + // is less than the cluster's timecode. Although in principle that + // is allowed, this muxer doesn't actually write clusters like that, + // so this indicates a bug somewhere in our algorithm. + + if (frame_timecode < last_cluster_timecode) // should never happen + return -1; + + // If the frame has a timestamp significantly larger than the last + // cluster (in Matroska, cluster-relative timestamps are serialized + // using a 16-bit signed integer), then we cannot write this frame + // to that cluster, and so we must create a new cluster. + + const int64_t delta_timecode = frame_timecode - last_cluster_timecode; + + if (delta_timecode > kMaxBlockTimecode) + return 2; + + // We decide to create a new cluster when we have a video keyframe. + // This will flush queued (audio) frames, and write the keyframe + // immediately, in the newly-created cluster. + + if (is_key && tracks_.TrackIsVideo(track_number)) + return 1; + + // Create a new cluster if we have accumulated too many frames + // already, where "too many" is defined as "the total time of frames + // in the cluster exceeds a threshold". + + const uint64_t delta_ns = delta_timecode * timecode_scale; + + if (max_cluster_duration_ > 0 && delta_ns >= max_cluster_duration_) + return 1; + + // This is similar to the case above, with the difference that a new + // cluster is created when the size of the current cluster exceeds a + // threshold. + + const uint64_t cluster_size = last_cluster->payload_size(); + + if (max_cluster_size_ > 0 && cluster_size >= max_cluster_size_) + return 1; + + // There's no need to create a new cluster, so emit this frame now. + + return 0; +} + +bool Segment::MakeNewCluster(uint64_t frame_timestamp_ns) { + const int32_t new_size = cluster_list_size_ + 1; + + if (new_size > cluster_list_capacity_) { + // Add more clusters. + const int32_t new_capacity = + (cluster_list_capacity_ <= 0) ? 1 : cluster_list_capacity_ * 2; + Cluster** const clusters = + new (std::nothrow) Cluster*[new_capacity]; // NOLINT + if (!clusters) + return false; + + for (int32_t i = 0; i < cluster_list_size_; ++i) { + clusters[i] = cluster_list_[i]; + } + + delete[] cluster_list_; + + cluster_list_ = clusters; + cluster_list_capacity_ = new_capacity; + } + + if (!WriteFramesLessThan(frame_timestamp_ns)) + return false; + + if (cluster_list_size_ > 0) { + // Update old cluster's size + Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1]; + + if (!old_cluster || !old_cluster->Finalize(true, frame_timestamp_ns)) + return false; + } + + if (output_cues_) + new_cuepoint_ = true; + + if (chunking_ && cluster_list_size_ > 0) { + chunk_writer_cluster_->Close(); + chunk_count_++; + + if (!UpdateChunkName("chk", &chunk_name_)) + return false; + if (!chunk_writer_cluster_->Open(chunk_name_)) + return false; + } + + const uint64_t timecode_scale = segment_info_.timecode_scale(); + const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale; + + uint64_t cluster_timecode = frame_timecode; + + if (frames_size_ > 0) { + const Frame* const f = frames_[0]; // earliest queued frame + const uint64_t ns = f->timestamp(); + const uint64_t tc = ns / timecode_scale; + + if (tc < cluster_timecode) + cluster_timecode = tc; + } + + Cluster*& cluster = cluster_list_[cluster_list_size_]; + const int64_t offset = MaxOffset(); + cluster = new (std::nothrow) + Cluster(cluster_timecode, offset, segment_info_.timecode_scale(), + accurate_cluster_duration_, fixed_size_cluster_timecode_); + if (!cluster) + return false; + + if (!cluster->Init(writer_cluster_)) + return false; + + cluster_list_size_ = new_size; + return true; +} + +bool Segment::DoNewClusterProcessing(uint64_t track_number, + uint64_t frame_timestamp_ns, bool is_key) { + for (;;) { + // Based on the characteristics of the current frame and current + // cluster, decide whether to create a new cluster. + const int result = TestFrame(track_number, frame_timestamp_ns, is_key); + if (result < 0) // error + return false; + + // Always set force_new_cluster_ to false after TestFrame. + force_new_cluster_ = false; + + // A non-zero result means create a new cluster. + if (result > 0 && !MakeNewCluster(frame_timestamp_ns)) + return false; + + // Write queued (audio) frames. + const int frame_count = WriteFramesAll(); + if (frame_count < 0) // error + return false; + + // Write the current frame to the current cluster (if TestFrame + // returns 0) or to a newly created cluster (TestFrame returns 1). + if (result <= 1) + return true; + + // TestFrame returned 2, which means there was a large time + // difference between the cluster and the frame itself. Do the + // test again, comparing the frame to the new cluster. + } +} + +bool Segment::CheckHeaderInfo() { + if (!header_written_) { + if (!WriteSegmentHeader()) + return false; + + if (!seek_head_.AddSeekEntry(libwebm::kMkvCluster, MaxOffset())) + return false; + + if (output_cues_ && cues_track_ == 0) { + // Check for a video track + for (uint32_t i = 0; i < tracks_.track_entries_size(); ++i) { + const Track* const track = tracks_.GetTrackByIndex(i); + if (!track) + return false; + + if (tracks_.TrackIsVideo(track->number())) { + cues_track_ = track->number(); + break; + } + } + + // Set first track found + if (cues_track_ == 0) { + const Track* const track = tracks_.GetTrackByIndex(0); + if (!track) + return false; + + cues_track_ = track->number(); + } + } + } + return true; +} + +void Segment::UpdateDocTypeVersion() { + for (uint32_t index = 0; index < tracks_.track_entries_size(); ++index) { + const Track* track = tracks_.GetTrackByIndex(index); + if (track == NULL) + break; + if ((track->codec_delay() || track->seek_pre_roll()) && + doc_type_version_ < 4) { + doc_type_version_ = 4; + break; + } + } +} + +bool Segment::UpdateChunkName(const char* ext, char** name) const { + if (!name || !ext) + return false; + + char ext_chk[64]; +#ifdef _MSC_VER + sprintf_s(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext); +#else + snprintf(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext); +#endif + + const size_t length = strlen(chunking_base_name_) + strlen(ext_chk) + 1; + char* const str = new (std::nothrow) char[length]; // NOLINT + if (!str) + return false; + +#ifdef _MSC_VER + strcpy_s(str, length - strlen(ext_chk), chunking_base_name_); + strcat_s(str, length, ext_chk); +#else + strcpy(str, chunking_base_name_); + strcat(str, ext_chk); +#endif + + delete[] * name; + *name = str; + + return true; +} + +int64_t Segment::MaxOffset() { + if (!writer_header_) + return -1; + + int64_t offset = writer_header_->Position() - payload_pos_; + + if (chunking_) { + for (int32_t i = 0; i < cluster_list_size_; ++i) { + Cluster* const cluster = cluster_list_[i]; + offset += cluster->Size(); + } + + if (writer_cues_) + offset += writer_cues_->Position(); + } + + return offset; +} + +bool Segment::QueueFrame(Frame* frame) { + const int32_t new_size = frames_size_ + 1; + + if (new_size > frames_capacity_) { + // Add more frames. + const int32_t new_capacity = (!frames_capacity_) ? 2 : frames_capacity_ * 2; + + if (new_capacity < 1) + return false; + + Frame** const frames = new (std::nothrow) Frame*[new_capacity]; // NOLINT + if (!frames) + return false; + + for (int32_t i = 0; i < frames_size_; ++i) { + frames[i] = frames_[i]; + } + + delete[] frames_; + frames_ = frames; + frames_capacity_ = new_capacity; + } + + frames_[frames_size_++] = frame; + + return true; +} + +int Segment::WriteFramesAll() { + if (frames_ == NULL) + return 0; + + if (cluster_list_size_ < 1) + return -1; + + Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; + + if (!cluster) + return -1; + + for (int32_t i = 0; i < frames_size_; ++i) { + Frame*& frame = frames_[i]; + // TODO(jzern/vigneshv): using Segment::AddGenericFrame here would limit the + // places where |doc_type_version_| needs to be updated. + if (frame->discard_padding() != 0) + doc_type_version_ = 4; + if (!cluster->AddFrame(frame)) + return -1; + + if (new_cuepoint_ && cues_track_ == frame->track_number()) { + if (!AddCuePoint(frame->timestamp(), cues_track_)) + return -1; + } + + if (frame->timestamp() > last_timestamp_) { + last_timestamp_ = frame->timestamp(); + last_track_timestamp_[frame->track_number() - 1] = frame->timestamp(); + } + + delete frame; + frame = NULL; + } + + const int result = frames_size_; + frames_size_ = 0; + + return result; +} + +bool Segment::WriteFramesLessThan(uint64_t timestamp) { + // Check |cluster_list_size_| to see if this is the first cluster. If it is + // the first cluster the audio frames that are less than the first video + // timesatmp will be written in a later step. + if (frames_size_ > 0 && cluster_list_size_ > 0) { + if (!frames_) + return false; + + Cluster* const cluster = cluster_list_[cluster_list_size_ - 1]; + if (!cluster) + return false; + + int32_t shift_left = 0; + + // TODO(fgalligan): Change this to use the durations of frames instead of + // the next frame's start time if the duration is accurate. + for (int32_t i = 1; i < frames_size_; ++i) { + const Frame* const frame_curr = frames_[i]; + + if (frame_curr->timestamp() > timestamp) + break; + + const Frame* const frame_prev = frames_[i - 1]; + if (frame_prev->discard_padding() != 0) + doc_type_version_ = 4; + if (!cluster->AddFrame(frame_prev)) + return false; + + if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) { + if (!AddCuePoint(frame_prev->timestamp(), cues_track_)) + return false; + } + + ++shift_left; + if (frame_prev->timestamp() > last_timestamp_) { + last_timestamp_ = frame_prev->timestamp(); + last_track_timestamp_[frame_prev->track_number() - 1] = + frame_prev->timestamp(); + } + + delete frame_prev; + } + + if (shift_left > 0) { + if (shift_left >= frames_size_) + return false; + + const int32_t new_frames_size = frames_size_ - shift_left; + for (int32_t i = 0; i < new_frames_size; ++i) { + frames_[i] = frames_[i + shift_left]; + } + + frames_size_ = new_frames_size; + } + } + + return true; +} + +bool Segment::DocTypeIsWebm() const { + const int kNumCodecIds = 9; + + // TODO(vigneshv): Tweak .clang-format. + const char* kWebmCodecIds[kNumCodecIds] = { + Tracks::kOpusCodecId, Tracks::kVorbisCodecId, + Tracks::kAv1CodecId, Tracks::kVp8CodecId, + Tracks::kVp9CodecId, Tracks::kWebVttCaptionsId, + Tracks::kWebVttDescriptionsId, Tracks::kWebVttMetadataId, + Tracks::kWebVttSubtitlesId}; + + const int num_tracks = static_cast<int>(tracks_.track_entries_size()); + for (int track_index = 0; track_index < num_tracks; ++track_index) { + const Track* const track = tracks_.GetTrackByIndex(track_index); + const std::string codec_id = track->codec_id(); + + bool id_is_webm = false; + for (int id_index = 0; id_index < kNumCodecIds; ++id_index) { + if (codec_id == kWebmCodecIds[id_index]) { + id_is_webm = true; + break; + } + } + + if (!id_is_webm) + return false; + } + + return true; +} + +} // namespace mkvmuxer diff --git a/mkvmuxer/mkvmuxer.h b/mkvmuxer/mkvmuxer.h new file mode 100644 index 0000000..f2db377 --- /dev/null +++ b/mkvmuxer/mkvmuxer.h @@ -0,0 +1,1924 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef MKVMUXER_MKVMUXER_H_ +#define MKVMUXER_MKVMUXER_H_ + +#include <stdint.h> + +#include <cstddef> +#include <list> +#include <map> + +#include "common/webmids.h" +#include "mkvmuxer/mkvmuxertypes.h" + +// For a description of the WebM elements see +// http://www.webmproject.org/code/specs/container/. + +namespace mkvparser { +class IMkvReader; +} // namespace mkvparser + +namespace mkvmuxer { + +class MkvWriter; +class Segment; + +const uint64_t kMaxTrackNumber = 126; + +/////////////////////////////////////////////////////////////// +// Interface used by the mkvmuxer to write out the Mkv data. +class IMkvWriter { + public: + // Writes out |len| bytes of |buf|. Returns 0 on success. + virtual int32 Write(const void* buf, uint32 len) = 0; + + // Returns the offset of the output position from the beginning of the + // output. + virtual int64 Position() const = 0; + + // Set the current File position. Returns 0 on success. + virtual int32 Position(int64 position) = 0; + + // Returns true if the writer is seekable. + virtual bool Seekable() const = 0; + + // Element start notification. Called whenever an element identifier is about + // to be written to the stream. |element_id| is the element identifier, and + // |position| is the location in the WebM stream where the first octet of the + // element identifier will be written. + // Note: the |MkvId| enumeration in webmids.hpp defines element values. + virtual void ElementStartNotify(uint64 element_id, int64 position) = 0; + + protected: + IMkvWriter(); + virtual ~IMkvWriter(); + + private: + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(IMkvWriter); +}; + +// Writes out the EBML header for a WebM file, but allows caller to specify +// DocType. This function must be called before any other libwebm writing +// functions are called. +bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version, + const char* const doc_type); + +// Writes out the EBML header for a WebM file. This function must be called +// before any other libwebm writing functions are called. +bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version); + +// Deprecated. Writes out EBML header with doc_type_version as +// kDefaultDocTypeVersion. Exists for backward compatibility. +bool WriteEbmlHeader(IMkvWriter* writer); + +// Copies in Chunk from source to destination between the given byte positions +bool ChunkedCopy(mkvparser::IMkvReader* source, IMkvWriter* dst, int64_t start, + int64_t size); + +/////////////////////////////////////////////////////////////// +// Class to hold data the will be written to a block. +class Frame { + public: + Frame(); + ~Frame(); + + // Sets this frame's contents based on |frame|. Returns true on success. On + // failure, this frame's existing contents may be lost. + bool CopyFrom(const Frame& frame); + + // Copies |frame| data into |frame_|. Returns true on success. + bool Init(const uint8_t* frame, uint64_t length); + + // Copies |additional| data into |additional_|. Returns true on success. + bool AddAdditionalData(const uint8_t* additional, uint64_t length, + uint64_t add_id); + + // Returns true if the frame has valid parameters. + bool IsValid() const; + + // Returns true if the frame can be written as a SimpleBlock based on current + // parameters. + bool CanBeSimpleBlock() const; + + uint64_t add_id() const { return add_id_; } + const uint8_t* additional() const { return additional_; } + uint64_t additional_length() const { return additional_length_; } + void set_duration(uint64_t duration); + uint64_t duration() const { return duration_; } + bool duration_set() const { return duration_set_; } + const uint8_t* frame() const { return frame_; } + void set_is_key(bool key) { is_key_ = key; } + bool is_key() const { return is_key_; } + uint64_t length() const { return length_; } + void set_track_number(uint64_t track_number) { track_number_ = track_number; } + uint64_t track_number() const { return track_number_; } + void set_timestamp(uint64_t timestamp) { timestamp_ = timestamp; } + uint64_t timestamp() const { return timestamp_; } + void set_discard_padding(int64_t discard_padding) { + discard_padding_ = discard_padding; + } + int64_t discard_padding() const { return discard_padding_; } + void set_reference_block_timestamp(int64_t reference_block_timestamp); + int64_t reference_block_timestamp() const { + return reference_block_timestamp_; + } + bool reference_block_timestamp_set() const { + return reference_block_timestamp_set_; + } + + private: + // Id of the Additional data. + uint64_t add_id_; + + // Pointer to additional data. Owned by this class. + uint8_t* additional_; + + // Length of the additional data. + uint64_t additional_length_; + + // Duration of the frame in nanoseconds. + uint64_t duration_; + + // Flag indicating that |duration_| has been set. Setting duration causes the + // frame to be written out as a Block with BlockDuration instead of as a + // SimpleBlock. + bool duration_set_; + + // Pointer to the data. Owned by this class. + uint8_t* frame_; + + // Flag telling if the data should set the key flag of a block. + bool is_key_; + + // Length of the data. + uint64_t length_; + + // Mkv track number the data is associated with. + uint64_t track_number_; + + // Timestamp of the data in nanoseconds. + uint64_t timestamp_; + + // Discard padding for the frame. + int64_t discard_padding_; + + // Reference block timestamp. + int64_t reference_block_timestamp_; + + // Flag indicating if |reference_block_timestamp_| has been set. + bool reference_block_timestamp_set_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Frame); +}; + +/////////////////////////////////////////////////////////////// +// Class to hold one cue point in a Cues element. +class CuePoint { + public: + CuePoint(); + ~CuePoint(); + + // Returns the size in bytes for the entire CuePoint element. + uint64_t Size() const; + + // Output the CuePoint element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + void set_time(uint64_t time) { time_ = time; } + uint64_t time() const { return time_; } + void set_track(uint64_t track) { track_ = track; } + uint64_t track() const { return track_; } + void set_cluster_pos(uint64_t cluster_pos) { cluster_pos_ = cluster_pos; } + uint64_t cluster_pos() const { return cluster_pos_; } + void set_block_number(uint64_t block_number) { block_number_ = block_number; } + uint64_t block_number() const { return block_number_; } + void set_output_block_number(bool output_block_number) { + output_block_number_ = output_block_number; + } + bool output_block_number() const { return output_block_number_; } + + private: + // Returns the size in bytes for the payload of the CuePoint element. + uint64_t PayloadSize() const; + + // Absolute timecode according to the segment time base. + uint64_t time_; + + // The Track element associated with the CuePoint. + uint64_t track_; + + // The position of the Cluster containing the Block. + uint64_t cluster_pos_; + + // Number of the Block within the Cluster, starting from 1. + uint64_t block_number_; + + // If true the muxer will write out the block number for the cue if the + // block number is different than the default of 1. Default is set to true. + bool output_block_number_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(CuePoint); +}; + +/////////////////////////////////////////////////////////////// +// Cues element. +class Cues { + public: + Cues(); + ~Cues(); + + // Adds a cue point to the Cues element. Returns true on success. + bool AddCue(CuePoint* cue); + + // Returns the cue point by index. Returns NULL if there is no cue point + // match. + CuePoint* GetCueByIndex(int32_t index) const; + + // Returns the total size of the Cues element + uint64_t Size(); + + // Output the Cues element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + int32_t cue_entries_size() const { return cue_entries_size_; } + void set_output_block_number(bool output_block_number) { + output_block_number_ = output_block_number; + } + bool output_block_number() const { return output_block_number_; } + + private: + // Number of allocated elements in |cue_entries_|. + int32_t cue_entries_capacity_; + + // Number of CuePoints in |cue_entries_|. + int32_t cue_entries_size_; + + // CuePoint list. + CuePoint** cue_entries_; + + // If true the muxer will write out the block number for the cue if the + // block number is different than the default of 1. Default is set to true. + bool output_block_number_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cues); +}; + +/////////////////////////////////////////////////////////////// +// ContentEncAESSettings element +class ContentEncAESSettings { + public: + enum { kCTR = 1 }; + + ContentEncAESSettings(); + ~ContentEncAESSettings() {} + + // Returns the size in bytes for the ContentEncAESSettings element. + uint64_t Size() const; + + // Writes out the ContentEncAESSettings element to |writer|. Returns true on + // success. + bool Write(IMkvWriter* writer) const; + + uint64_t cipher_mode() const { return cipher_mode_; } + + private: + // Returns the size in bytes for the payload of the ContentEncAESSettings + // element. + uint64_t PayloadSize() const; + + // Sub elements + uint64_t cipher_mode_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncAESSettings); +}; + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +// Elements used to describe if the track data has been encrypted or +// compressed with zlib or header stripping. +// Currently only whole frames can be encrypted with AES. This dictates that +// ContentEncodingOrder will be 0, ContentEncodingScope will be 1, +// ContentEncodingType will be 1, and ContentEncAlgo will be 5. +class ContentEncoding { + public: + ContentEncoding(); + ~ContentEncoding(); + + // Sets the content encryption id. Copies |length| bytes from |id| to + // |enc_key_id_|. Returns true on success. + bool SetEncryptionID(const uint8_t* id, uint64_t length); + + // Returns the size in bytes for the ContentEncoding element. + uint64_t Size() const; + + // Writes out the ContentEncoding element to |writer|. Returns true on + // success. + bool Write(IMkvWriter* writer) const; + + uint64_t enc_algo() const { return enc_algo_; } + uint64_t encoding_order() const { return encoding_order_; } + uint64_t encoding_scope() const { return encoding_scope_; } + uint64_t encoding_type() const { return encoding_type_; } + ContentEncAESSettings* enc_aes_settings() { return &enc_aes_settings_; } + + private: + // Returns the size in bytes for the encoding elements. + uint64_t EncodingSize(uint64_t compresion_size, + uint64_t encryption_size) const; + + // Returns the size in bytes for the encryption elements. + uint64_t EncryptionSize() const; + + // Track element names + uint64_t enc_algo_; + uint8_t* enc_key_id_; + uint64_t encoding_order_; + uint64_t encoding_scope_; + uint64_t encoding_type_; + + // ContentEncAESSettings element. + ContentEncAESSettings enc_aes_settings_; + + // Size of the ContentEncKeyID data in bytes. + uint64_t enc_key_id_length_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); +}; + +/////////////////////////////////////////////////////////////// +// Colour element. +class PrimaryChromaticity { + public: + static const float kChromaticityMin; + static const float kChromaticityMax; + + PrimaryChromaticity(float x_val, float y_val) : x_(x_val), y_(y_val) {} + PrimaryChromaticity() : x_(0), y_(0) {} + ~PrimaryChromaticity() {} + + // Returns sum of |x_id| and |y_id| element id sizes and payload sizes. + uint64_t PrimaryChromaticitySize(libwebm::MkvId x_id, + libwebm::MkvId y_id) const; + bool Valid() const; + bool Write(IMkvWriter* writer, libwebm::MkvId x_id, + libwebm::MkvId y_id) const; + + float x() const { return x_; } + void set_x(float new_x) { x_ = new_x; } + float y() const { return y_; } + void set_y(float new_y) { y_ = new_y; } + + private: + float x_; + float y_; +}; + +class MasteringMetadata { + public: + static const float kValueNotPresent; + static const float kMinLuminance; + static const float kMinLuminanceMax; + static const float kMaxLuminanceMax; + + MasteringMetadata() + : luminance_max_(kValueNotPresent), + luminance_min_(kValueNotPresent), + r_(NULL), + g_(NULL), + b_(NULL), + white_point_(NULL) {} + ~MasteringMetadata() { + delete r_; + delete g_; + delete b_; + delete white_point_; + } + + // Returns total size of the MasteringMetadata element. + uint64_t MasteringMetadataSize() const; + bool Valid() const; + bool Write(IMkvWriter* writer) const; + + // Copies non-null chromaticity. + bool SetChromaticity(const PrimaryChromaticity* r, + const PrimaryChromaticity* g, + const PrimaryChromaticity* b, + const PrimaryChromaticity* white_point); + const PrimaryChromaticity* r() const { return r_; } + const PrimaryChromaticity* g() const { return g_; } + const PrimaryChromaticity* b() const { return b_; } + const PrimaryChromaticity* white_point() const { return white_point_; } + + float luminance_max() const { return luminance_max_; } + void set_luminance_max(float luminance_max) { + luminance_max_ = luminance_max; + } + float luminance_min() const { return luminance_min_; } + void set_luminance_min(float luminance_min) { + luminance_min_ = luminance_min; + } + + private: + // Returns size of MasteringMetadata child elements. + uint64_t PayloadSize() const; + + float luminance_max_; + float luminance_min_; + PrimaryChromaticity* r_; + PrimaryChromaticity* g_; + PrimaryChromaticity* b_; + PrimaryChromaticity* white_point_; +}; + +class Colour { + public: + enum MatrixCoefficients { + kGbr = 0, + kBt709 = 1, + kUnspecifiedMc = 2, + kReserved = 3, + kFcc = 4, + kBt470bg = 5, + kSmpte170MMc = 6, + kSmpte240MMc = 7, + kYcocg = 8, + kBt2020NonConstantLuminance = 9, + kBt2020ConstantLuminance = 10, + }; + enum ChromaSitingHorz { + kUnspecifiedCsh = 0, + kLeftCollocated = 1, + kHalfCsh = 2, + }; + enum ChromaSitingVert { + kUnspecifiedCsv = 0, + kTopCollocated = 1, + kHalfCsv = 2, + }; + enum Range { + kUnspecifiedCr = 0, + kBroadcastRange = 1, + kFullRange = 2, + kMcTcDefined = 3, // Defined by MatrixCoefficients/TransferCharacteristics. + }; + enum TransferCharacteristics { + kIturBt709Tc = 1, + kUnspecifiedTc = 2, + kReservedTc = 3, + kGamma22Curve = 4, + kGamma28Curve = 5, + kSmpte170MTc = 6, + kSmpte240MTc = 7, + kLinear = 8, + kLog = 9, + kLogSqrt = 10, + kIec6196624 = 11, + kIturBt1361ExtendedColourGamut = 12, + kIec6196621 = 13, + kIturBt202010bit = 14, + kIturBt202012bit = 15, + kSmpteSt2084 = 16, + kSmpteSt4281Tc = 17, + kAribStdB67Hlg = 18, + }; + enum Primaries { + kReservedP0 = 0, + kIturBt709P = 1, + kUnspecifiedP = 2, + kReservedP3 = 3, + kIturBt470M = 4, + kIturBt470Bg = 5, + kSmpte170MP = 6, + kSmpte240MP = 7, + kFilm = 8, + kIturBt2020 = 9, + kSmpteSt4281P = 10, + kJedecP22Phosphors = 22, + }; + static const uint64_t kValueNotPresent; + Colour() + : matrix_coefficients_(kValueNotPresent), + bits_per_channel_(kValueNotPresent), + chroma_subsampling_horz_(kValueNotPresent), + chroma_subsampling_vert_(kValueNotPresent), + cb_subsampling_horz_(kValueNotPresent), + cb_subsampling_vert_(kValueNotPresent), + chroma_siting_horz_(kValueNotPresent), + chroma_siting_vert_(kValueNotPresent), + range_(kValueNotPresent), + transfer_characteristics_(kValueNotPresent), + primaries_(kValueNotPresent), + max_cll_(kValueNotPresent), + max_fall_(kValueNotPresent), + mastering_metadata_(NULL) {} + ~Colour() { delete mastering_metadata_; } + + // Returns total size of the Colour element. + uint64_t ColourSize() const; + bool Valid() const; + bool Write(IMkvWriter* writer) const; + + // Deep copies |mastering_metadata|. + bool SetMasteringMetadata(const MasteringMetadata& mastering_metadata); + + const MasteringMetadata* mastering_metadata() const { + return mastering_metadata_; + } + + uint64_t matrix_coefficients() const { return matrix_coefficients_; } + void set_matrix_coefficients(uint64_t matrix_coefficients) { + matrix_coefficients_ = matrix_coefficients; + } + uint64_t bits_per_channel() const { return bits_per_channel_; } + void set_bits_per_channel(uint64_t bits_per_channel) { + bits_per_channel_ = bits_per_channel; + } + uint64_t chroma_subsampling_horz() const { return chroma_subsampling_horz_; } + void set_chroma_subsampling_horz(uint64_t chroma_subsampling_horz) { + chroma_subsampling_horz_ = chroma_subsampling_horz; + } + uint64_t chroma_subsampling_vert() const { return chroma_subsampling_vert_; } + void set_chroma_subsampling_vert(uint64_t chroma_subsampling_vert) { + chroma_subsampling_vert_ = chroma_subsampling_vert; + } + uint64_t cb_subsampling_horz() const { return cb_subsampling_horz_; } + void set_cb_subsampling_horz(uint64_t cb_subsampling_horz) { + cb_subsampling_horz_ = cb_subsampling_horz; + } + uint64_t cb_subsampling_vert() const { return cb_subsampling_vert_; } + void set_cb_subsampling_vert(uint64_t cb_subsampling_vert) { + cb_subsampling_vert_ = cb_subsampling_vert; + } + uint64_t chroma_siting_horz() const { return chroma_siting_horz_; } + void set_chroma_siting_horz(uint64_t chroma_siting_horz) { + chroma_siting_horz_ = chroma_siting_horz; + } + uint64_t chroma_siting_vert() const { return chroma_siting_vert_; } + void set_chroma_siting_vert(uint64_t chroma_siting_vert) { + chroma_siting_vert_ = chroma_siting_vert; + } + uint64_t range() const { return range_; } + void set_range(uint64_t range) { range_ = range; } + uint64_t transfer_characteristics() const { + return transfer_characteristics_; + } + void set_transfer_characteristics(uint64_t transfer_characteristics) { + transfer_characteristics_ = transfer_characteristics; + } + uint64_t primaries() const { return primaries_; } + void set_primaries(uint64_t primaries) { primaries_ = primaries; } + uint64_t max_cll() const { return max_cll_; } + void set_max_cll(uint64_t max_cll) { max_cll_ = max_cll; } + uint64_t max_fall() const { return max_fall_; } + void set_max_fall(uint64_t max_fall) { max_fall_ = max_fall; } + + private: + // Returns size of Colour child elements. + uint64_t PayloadSize() const; + + uint64_t matrix_coefficients_; + uint64_t bits_per_channel_; + uint64_t chroma_subsampling_horz_; + uint64_t chroma_subsampling_vert_; + uint64_t cb_subsampling_horz_; + uint64_t cb_subsampling_vert_; + uint64_t chroma_siting_horz_; + uint64_t chroma_siting_vert_; + uint64_t range_; + uint64_t transfer_characteristics_; + uint64_t primaries_; + uint64_t max_cll_; + uint64_t max_fall_; + + MasteringMetadata* mastering_metadata_; +}; + +/////////////////////////////////////////////////////////////// +// Projection element. +class Projection { + public: + enum ProjectionType { + kTypeNotPresent = -1, + kRectangular = 0, + kEquirectangular = 1, + kCubeMap = 2, + kMesh = 3, + }; + static const uint64_t kValueNotPresent; + Projection() + : type_(kRectangular), + pose_yaw_(0.0), + pose_pitch_(0.0), + pose_roll_(0.0), + private_data_(NULL), + private_data_length_(0) {} + ~Projection() { delete[] private_data_; } + + uint64_t ProjectionSize() const; + bool Write(IMkvWriter* writer) const; + + bool SetProjectionPrivate(const uint8_t* private_data, + uint64_t private_data_length); + + ProjectionType type() const { return type_; } + void set_type(ProjectionType type) { type_ = type; } + float pose_yaw() const { return pose_yaw_; } + void set_pose_yaw(float pose_yaw) { pose_yaw_ = pose_yaw; } + float pose_pitch() const { return pose_pitch_; } + void set_pose_pitch(float pose_pitch) { pose_pitch_ = pose_pitch; } + float pose_roll() const { return pose_roll_; } + void set_pose_roll(float pose_roll) { pose_roll_ = pose_roll; } + uint8_t* private_data() const { return private_data_; } + uint64_t private_data_length() const { return private_data_length_; } + + private: + // Returns size of VideoProjection child elements. + uint64_t PayloadSize() const; + + ProjectionType type_; + float pose_yaw_; + float pose_pitch_; + float pose_roll_; + uint8_t* private_data_; + uint64_t private_data_length_; +}; + +/////////////////////////////////////////////////////////////// +// Track element. +class Track { + public: + // The |seed| parameter is used to synthesize a UID for the track. + explicit Track(unsigned int* seed); + virtual ~Track(); + + // Adds a ContentEncoding element to the Track. Returns true on success. + virtual bool AddContentEncoding(); + + // Returns the ContentEncoding by index. Returns NULL if there is no + // ContentEncoding match. + ContentEncoding* GetContentEncodingByIndex(uint32_t index) const; + + // Returns the size in bytes for the payload of the Track element. + virtual uint64_t PayloadSize() const; + + // Returns the size in bytes of the Track element. + virtual uint64_t Size() const; + + // Output the Track element to the writer. Returns true on success. + virtual bool Write(IMkvWriter* writer) const; + + // Sets the CodecPrivate element of the Track element. Copies |length| + // bytes from |codec_private| to |codec_private_|. Returns true on success. + bool SetCodecPrivate(const uint8_t* codec_private, uint64_t length); + + void set_codec_id(const char* codec_id); + const char* codec_id() const { return codec_id_; } + const uint8_t* codec_private() const { return codec_private_; } + void set_language(const char* language); + const char* language() const { return language_; } + void set_max_block_additional_id(uint64_t max_block_additional_id) { + max_block_additional_id_ = max_block_additional_id; + } + uint64_t max_block_additional_id() const { return max_block_additional_id_; } + void set_name(const char* name); + const char* name() const { return name_; } + void set_number(uint64_t number) { number_ = number; } + uint64_t number() const { return number_; } + void set_type(uint64_t type) { type_ = type; } + uint64_t type() const { return type_; } + void set_uid(uint64_t uid) { uid_ = uid; } + uint64_t uid() const { return uid_; } + void set_codec_delay(uint64_t codec_delay) { codec_delay_ = codec_delay; } + uint64_t codec_delay() const { return codec_delay_; } + void set_seek_pre_roll(uint64_t seek_pre_roll) { + seek_pre_roll_ = seek_pre_roll; + } + uint64_t seek_pre_roll() const { return seek_pre_roll_; } + void set_default_duration(uint64_t default_duration) { + default_duration_ = default_duration; + } + uint64_t default_duration() const { return default_duration_; } + + uint64_t codec_private_length() const { return codec_private_length_; } + uint32_t content_encoding_entries_size() const { + return content_encoding_entries_size_; + } + + private: + // Track element names. + char* codec_id_; + uint8_t* codec_private_; + char* language_; + uint64_t max_block_additional_id_; + char* name_; + uint64_t number_; + uint64_t type_; + uint64_t uid_; + uint64_t codec_delay_; + uint64_t seek_pre_roll_; + uint64_t default_duration_; + + // Size of the CodecPrivate data in bytes. + uint64_t codec_private_length_; + + // ContentEncoding element list. + ContentEncoding** content_encoding_entries_; + + // Number of ContentEncoding elements added. + uint32_t content_encoding_entries_size_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Track); +}; + +/////////////////////////////////////////////////////////////// +// Track that has video specific elements. +class VideoTrack : public Track { + public: + // Supported modes for stereo 3D. + enum StereoMode { + kMono = 0, + kSideBySideLeftIsFirst = 1, + kTopBottomRightIsFirst = 2, + kTopBottomLeftIsFirst = 3, + kSideBySideRightIsFirst = 11 + }; + + enum AlphaMode { kNoAlpha = 0, kAlpha = 1 }; + + // The |seed| parameter is used to synthesize a UID for the track. + explicit VideoTrack(unsigned int* seed); + virtual ~VideoTrack(); + + // Returns the size in bytes for the payload of the Track element plus the + // video specific elements. + virtual uint64_t PayloadSize() const; + + // Output the VideoTrack element to the writer. Returns true on success. + virtual bool Write(IMkvWriter* writer) const; + + // Sets the video's stereo mode. Returns true on success. + bool SetStereoMode(uint64_t stereo_mode); + + // Sets the video's alpha mode. Returns true on success. + bool SetAlphaMode(uint64_t alpha_mode); + + void set_display_height(uint64_t height) { display_height_ = height; } + uint64_t display_height() const { return display_height_; } + void set_display_width(uint64_t width) { display_width_ = width; } + uint64_t display_width() const { return display_width_; } + void set_pixel_height(uint64_t height) { pixel_height_ = height; } + uint64_t pixel_height() const { return pixel_height_; } + void set_pixel_width(uint64_t width) { pixel_width_ = width; } + uint64_t pixel_width() const { return pixel_width_; } + + void set_crop_left(uint64_t crop_left) { crop_left_ = crop_left; } + uint64_t crop_left() const { return crop_left_; } + void set_crop_right(uint64_t crop_right) { crop_right_ = crop_right; } + uint64_t crop_right() const { return crop_right_; } + void set_crop_top(uint64_t crop_top) { crop_top_ = crop_top; } + uint64_t crop_top() const { return crop_top_; } + void set_crop_bottom(uint64_t crop_bottom) { crop_bottom_ = crop_bottom; } + uint64_t crop_bottom() const { return crop_bottom_; } + + void set_frame_rate(double frame_rate) { frame_rate_ = frame_rate; } + double frame_rate() const { return frame_rate_; } + void set_height(uint64_t height) { height_ = height; } + uint64_t height() const { return height_; } + uint64_t stereo_mode() { return stereo_mode_; } + uint64_t alpha_mode() { return alpha_mode_; } + void set_width(uint64_t width) { width_ = width; } + uint64_t width() const { return width_; } + void set_colour_space(const char* colour_space); + const char* colour_space() const { return colour_space_; } + + Colour* colour() { return colour_; } + + // Deep copies |colour|. + bool SetColour(const Colour& colour); + + Projection* projection() { return projection_; } + + // Deep copies |projection|. + bool SetProjection(const Projection& projection); + + private: + // Returns the size in bytes of the Video element. + uint64_t VideoPayloadSize() const; + + // Video track element names. + uint64_t display_height_; + uint64_t display_width_; + uint64_t pixel_height_; + uint64_t pixel_width_; + uint64_t crop_left_; + uint64_t crop_right_; + uint64_t crop_top_; + uint64_t crop_bottom_; + double frame_rate_; + uint64_t height_; + uint64_t stereo_mode_; + uint64_t alpha_mode_; + uint64_t width_; + char* colour_space_; + + Colour* colour_; + Projection* projection_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(VideoTrack); +}; + +/////////////////////////////////////////////////////////////// +// Track that has audio specific elements. +class AudioTrack : public Track { + public: + // The |seed| parameter is used to synthesize a UID for the track. + explicit AudioTrack(unsigned int* seed); + virtual ~AudioTrack(); + + // Returns the size in bytes for the payload of the Track element plus the + // audio specific elements. + virtual uint64_t PayloadSize() const; + + // Output the AudioTrack element to the writer. Returns true on success. + virtual bool Write(IMkvWriter* writer) const; + + void set_bit_depth(uint64_t bit_depth) { bit_depth_ = bit_depth; } + uint64_t bit_depth() const { return bit_depth_; } + void set_channels(uint64_t channels) { channels_ = channels; } + uint64_t channels() const { return channels_; } + void set_sample_rate(double sample_rate) { sample_rate_ = sample_rate; } + double sample_rate() const { return sample_rate_; } + + private: + // Audio track element names. + uint64_t bit_depth_; + uint64_t channels_; + double sample_rate_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(AudioTrack); +}; + +/////////////////////////////////////////////////////////////// +// Tracks element +class Tracks { + public: + // Audio and video type defined by the Matroska specs. + enum { kVideo = 0x1, kAudio = 0x2 }; + + static const char kOpusCodecId[]; + static const char kVorbisCodecId[]; + static const char kAv1CodecId[]; + static const char kVp8CodecId[]; + static const char kVp9CodecId[]; + static const char kWebVttCaptionsId[]; + static const char kWebVttDescriptionsId[]; + static const char kWebVttMetadataId[]; + static const char kWebVttSubtitlesId[]; + + Tracks(); + ~Tracks(); + + // Adds a Track element to the Tracks object. |track| will be owned and + // deleted by the Tracks object. Returns true on success. |number| is the + // number to use for the track. |number| must be >= 0. If |number| == 0 + // then the muxer will decide on the track number. + bool AddTrack(Track* track, int32_t number); + + // Returns the track by index. Returns NULL if there is no track match. + const Track* GetTrackByIndex(uint32_t idx) const; + + // Search the Tracks and return the track that matches |tn|. Returns NULL + // if there is no track match. + Track* GetTrackByNumber(uint64_t track_number) const; + + // Returns true if the track number is an audio track. + bool TrackIsAudio(uint64_t track_number) const; + + // Returns true if the track number is a video track. + bool TrackIsVideo(uint64_t track_number) const; + + // Output the Tracks element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + uint32_t track_entries_size() const { return track_entries_size_; } + + private: + // Track element list. + Track** track_entries_; + + // Number of Track elements added. + uint32_t track_entries_size_; + + // Whether or not Tracks element has already been written via IMkvWriter. + mutable bool wrote_tracks_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tracks); +}; + +/////////////////////////////////////////////////////////////// +// Chapter element +// +class Chapter { + public: + // Set the identifier for this chapter. (This corresponds to the + // Cue Identifier line in WebVTT.) + // TODO(matthewjheaney): the actual serialization of this item in + // MKV is pending. + bool set_id(const char* id); + + // Converts the nanosecond start and stop times of this chapter to + // their corresponding timecode values, and stores them that way. + void set_time(const Segment& segment, uint64_t start_time_ns, + uint64_t end_time_ns); + + // Sets the uid for this chapter. Primarily used to enable + // deterministic output from the muxer. + void set_uid(const uint64_t uid) { uid_ = uid; } + + // Add a title string to this chapter, per the semantics described + // here: + // http://www.matroska.org/technical/specs/index.html + // + // The title ("chapter string") is a UTF-8 string. + // + // The language has ISO 639-2 representation, described here: + // http://www.loc.gov/standards/iso639-2/englangn.html + // http://www.loc.gov/standards/iso639-2/php/English_list.php + // If you specify NULL as the language value, this implies + // English ("eng"). + // + // The country value corresponds to the codes listed here: + // http://www.iana.org/domains/root/db/ + // + // The function returns false if the string could not be allocated. + bool add_string(const char* title, const char* language, const char* country); + + private: + friend class Chapters; + + // For storage of chapter titles that differ by language. + class Display { + public: + // Establish representation invariant for new Display object. + void Init(); + + // Reclaim resources, in anticipation of destruction. + void Clear(); + + // Copies the title to the |title_| member. Returns false on + // error. + bool set_title(const char* title); + + // Copies the language to the |language_| member. Returns false + // on error. + bool set_language(const char* language); + + // Copies the country to the |country_| member. Returns false on + // error. + bool set_country(const char* country); + + // If |writer| is non-NULL, serialize the Display sub-element of + // the Atom into the stream. Returns the Display element size on + // success, 0 if error. + uint64_t WriteDisplay(IMkvWriter* writer) const; + + private: + char* title_; + char* language_; + char* country_; + }; + + Chapter(); + ~Chapter(); + + // Establish the representation invariant for a newly-created + // Chapter object. The |seed| parameter is used to create the UID + // for this chapter atom. + void Init(unsigned int* seed); + + // Copies this Chapter object to a different one. This is used when + // expanding a plain array of Chapter objects (see Chapters). + void ShallowCopy(Chapter* dst) const; + + // Reclaim resources used by this Chapter object, pending its + // destruction. + void Clear(); + + // If there is no storage remaining on the |displays_| array for a + // new display object, creates a new, longer array and copies the + // existing Display objects to the new array. Returns false if the + // array cannot be expanded. + bool ExpandDisplaysArray(); + + // If |writer| is non-NULL, serialize the Atom sub-element into the + // stream. Returns the total size of the element on success, 0 if + // error. + uint64_t WriteAtom(IMkvWriter* writer) const; + + // The string identifier for this chapter (corresponds to WebVTT cue + // identifier). + char* id_; + + // Start timecode of the chapter. + uint64_t start_timecode_; + + // Stop timecode of the chapter. + uint64_t end_timecode_; + + // The binary identifier for this chapter. + uint64_t uid_; + + // The Atom element can contain multiple Display sub-elements, as + // the same logical title can be rendered in different languages. + Display* displays_; + + // The physical length (total size) of the |displays_| array. + int displays_size_; + + // The logical length (number of active elements) on the |displays_| + // array. + int displays_count_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapter); +}; + +/////////////////////////////////////////////////////////////// +// Chapters element +// +class Chapters { + public: + Chapters(); + ~Chapters(); + + Chapter* AddChapter(unsigned int* seed); + + // Returns the number of chapters that have been added. + int Count() const; + + // Output the Chapters element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + private: + // Expands the chapters_ array if there is not enough space to contain + // another chapter object. Returns true on success. + bool ExpandChaptersArray(); + + // If |writer| is non-NULL, serialize the Edition sub-element of the + // Chapters element into the stream. Returns the Edition element + // size on success, 0 if error. + uint64_t WriteEdition(IMkvWriter* writer) const; + + // Total length of the chapters_ array. + int chapters_size_; + + // Number of active chapters on the chapters_ array. + int chapters_count_; + + // Array for storage of chapter objects. + Chapter* chapters_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapters); +}; + +/////////////////////////////////////////////////////////////// +// Tag element +// +class Tag { + public: + bool add_simple_tag(const char* tag_name, const char* tag_string); + + private: + // Tags calls Clear and the destructor of Tag + friend class Tags; + + // For storage of simple tags + class SimpleTag { + public: + // Establish representation invariant for new SimpleTag object. + void Init(); + + // Reclaim resources, in anticipation of destruction. + void Clear(); + + // Copies the title to the |tag_name_| member. Returns false on + // error. + bool set_tag_name(const char* tag_name); + + // Copies the language to the |tag_string_| member. Returns false + // on error. + bool set_tag_string(const char* tag_string); + + // If |writer| is non-NULL, serialize the SimpleTag sub-element of + // the Atom into the stream. Returns the SimpleTag element size on + // success, 0 if error. + uint64_t Write(IMkvWriter* writer) const; + + private: + char* tag_name_; + char* tag_string_; + }; + + Tag(); + ~Tag(); + + // Copies this Tag object to a different one. This is used when + // expanding a plain array of Tag objects (see Tags). + void ShallowCopy(Tag* dst) const; + + // Reclaim resources used by this Tag object, pending its + // destruction. + void Clear(); + + // If there is no storage remaining on the |simple_tags_| array for a + // new display object, creates a new, longer array and copies the + // existing SimpleTag objects to the new array. Returns false if the + // array cannot be expanded. + bool ExpandSimpleTagsArray(); + + // If |writer| is non-NULL, serialize the Tag sub-element into the + // stream. Returns the total size of the element on success, 0 if + // error. + uint64_t Write(IMkvWriter* writer) const; + + // The Atom element can contain multiple SimpleTag sub-elements + SimpleTag* simple_tags_; + + // The physical length (total size) of the |simple_tags_| array. + int simple_tags_size_; + + // The logical length (number of active elements) on the |simple_tags_| + // array. + int simple_tags_count_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tag); +}; + +/////////////////////////////////////////////////////////////// +// Tags element +// +class Tags { + public: + Tags(); + ~Tags(); + + Tag* AddTag(); + + // Returns the number of tags that have been added. + int Count() const; + + // Output the Tags element to the writer. Returns true on success. + bool Write(IMkvWriter* writer) const; + + private: + // Expands the tags_ array if there is not enough space to contain + // another tag object. Returns true on success. + bool ExpandTagsArray(); + + // Total length of the tags_ array. + int tags_size_; + + // Number of active tags on the tags_ array. + int tags_count_; + + // Array for storage of tag objects. + Tag* tags_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tags); +}; + +/////////////////////////////////////////////////////////////// +// Cluster element +// +// Notes: +// |Init| must be called before any other method in this class. +class Cluster { + public: + // |timecode| is the absolute timecode of the cluster. |cues_pos| is the + // position for the cluster within the segment that should be written in + // the cues element. |timecode_scale| is the timecode scale of the segment. + Cluster(uint64_t timecode, int64_t cues_pos, uint64_t timecode_scale, + bool write_last_frame_with_duration = false, + bool fixed_size_timecode = false); + ~Cluster(); + + bool Init(IMkvWriter* ptr_writer); + + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + bool AddFrame(const Frame* frame); + + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + // Inputs: + // data: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // timecode: Absolute (not relative to cluster) timestamp of the + // frame, expressed in timecode units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrame(const uint8_t* data, uint64_t length, uint64_t track_number, + uint64_t timecode, // timecode units (absolute) + bool is_key); + + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + // Inputs: + // data: Pointer to the data + // length: Length of the data + // additional: Pointer to the additional data + // additional_length: Length of the additional data + // add_id: Value of BlockAddID element + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // abs_timecode: Absolute (not relative to cluster) timestamp of the + // frame, expressed in timecode units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithAdditional(const uint8_t* data, uint64_t length, + const uint8_t* additional, + uint64_t additional_length, uint64_t add_id, + uint64_t track_number, uint64_t abs_timecode, + bool is_key); + + // Adds a frame to be output in the file. The frame is written out through + // |writer_| if successful. Returns true on success. + // Inputs: + // data: Pointer to the data. + // length: Length of the data. + // discard_padding: DiscardPadding element value. + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // abs_timecode: Absolute (not relative to cluster) timestamp of the + // frame, expressed in timecode units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length, + int64_t discard_padding, + uint64_t track_number, uint64_t abs_timecode, + bool is_key); + + // Writes a frame of metadata to the output medium; returns true on + // success. + // Inputs: + // data: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. The range of allowed values is [1, 126]. + // timecode: Absolute (not relative to cluster) timestamp of the + // metadata frame, expressed in timecode units. + // duration: Duration of metadata frame, in timecode units. + // + // The metadata frame is written as a block group, with a duration + // sub-element but no reference time sub-elements (indicating that + // it is considered a keyframe, per Matroska semantics). + bool AddMetadata(const uint8_t* data, uint64_t length, uint64_t track_number, + uint64_t timecode, uint64_t duration); + + // Increments the size of the cluster's data in bytes. + void AddPayloadSize(uint64_t size); + + // Closes the cluster so no more data can be written to it. Will update the + // cluster's size if |writer_| is seekable. Returns true on success. This + // variant of Finalize() fails when |write_last_frame_with_duration_| is set + // to true. + bool Finalize(); + + // Closes the cluster so no more data can be written to it. Will update the + // cluster's size if |writer_| is seekable. Returns true on success. + // Inputs: + // set_last_frame_duration: Boolean indicating whether or not the duration + // of the last frame should be set. If set to + // false, the |duration| value is ignored and + // |write_last_frame_with_duration_| will not be + // honored. + // duration: Duration of the Cluster in timecode scale. + bool Finalize(bool set_last_frame_duration, uint64_t duration); + + // Returns the size in bytes for the entire Cluster element. + uint64_t Size() const; + + // Given |abs_timecode|, calculates timecode relative to most recent timecode. + // Returns -1 on failure, or a relative timecode. + int64_t GetRelativeTimecode(int64_t abs_timecode) const; + + int64_t size_position() const { return size_position_; } + int32_t blocks_added() const { return blocks_added_; } + uint64_t payload_size() const { return payload_size_; } + int64_t position_for_cues() const { return position_for_cues_; } + uint64_t timecode() const { return timecode_; } + uint64_t timecode_scale() const { return timecode_scale_; } + void set_write_last_frame_with_duration(bool write_last_frame_with_duration) { + write_last_frame_with_duration_ = write_last_frame_with_duration; + } + bool write_last_frame_with_duration() const { + return write_last_frame_with_duration_; + } + + private: + // Iterator type for the |stored_frames_| map. + typedef std::map<uint64_t, std::list<Frame*> >::iterator FrameMapIterator; + + // Utility method that confirms that blocks can still be added, and that the + // cluster header has been written. Used by |DoWriteFrame*|. Returns true + // when successful. + bool PreWriteBlock(); + + // Utility method used by the |DoWriteFrame*| methods that handles the book + // keeping required after each block is written. + void PostWriteBlock(uint64_t element_size); + + // Does some verification and calls WriteFrame. + bool DoWriteFrame(const Frame* const frame); + + // Either holds back the given frame, or writes it out depending on whether or + // not |write_last_frame_with_duration_| is set. + bool QueueOrWriteFrame(const Frame* const frame); + + // Outputs the Cluster header to |writer_|. Returns true on success. + bool WriteClusterHeader(); + + // Number of blocks added to the cluster. + int32_t blocks_added_; + + // Flag telling if the cluster has been closed. + bool finalized_; + + // Flag indicating whether the cluster's timecode will always be written out + // using 8 bytes. + bool fixed_size_timecode_; + + // Flag telling if the cluster's header has been written. + bool header_written_; + + // The size of the cluster elements in bytes. + uint64_t payload_size_; + + // The file position used for cue points. + const int64_t position_for_cues_; + + // The file position of the cluster's size element. + int64_t size_position_; + + // The absolute timecode of the cluster. + const uint64_t timecode_; + + // The timecode scale of the Segment containing the cluster. + const uint64_t timecode_scale_; + + // Flag indicating whether the last frame of the cluster should be written as + // a Block with Duration. If set to true, then it will result in holding back + // of frames and the parameterized version of Finalize() must be called to + // finish writing the Cluster. + bool write_last_frame_with_duration_; + + // Map used to hold back frames, if required. Track number is the key. + std::map<uint64_t, std::list<Frame*> > stored_frames_; + + // Map from track number to the timestamp of the last block written for that + // track. + std::map<uint64_t, uint64_t> last_block_timestamp_; + + // Pointer to the writer object. Not owned by this class. + IMkvWriter* writer_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cluster); +}; + +/////////////////////////////////////////////////////////////// +// SeekHead element +class SeekHead { + public: + SeekHead(); + ~SeekHead(); + + // TODO(fgalligan): Change this to reserve a certain size. Then check how + // big the seek entry to be added is as not every seek entry will be the + // maximum size it could be. + // Adds a seek entry to be written out when the element is finalized. |id| + // must be the coded mkv element id. |pos| is the file position of the + // element. Returns true on success. + bool AddSeekEntry(uint32_t id, uint64_t pos); + + // Writes out SeekHead and SeekEntry elements. Returns true on success. + bool Finalize(IMkvWriter* writer) const; + + // Returns the id of the Seek Entry at the given index. Returns -1 if index is + // out of range. + uint32_t GetId(int index) const; + + // Returns the position of the Seek Entry at the given index. Returns -1 if + // index is out of range. + uint64_t GetPosition(int index) const; + + // Sets the Seek Entry id and position at given index. + // Returns true on success. + bool SetSeekEntry(int index, uint32_t id, uint64_t position); + + // Reserves space by writing out a Void element which will be updated with + // a SeekHead element later. Returns true on success. + bool Write(IMkvWriter* writer); + + // We are going to put a cap on the number of Seek Entries. + const static int32_t kSeekEntryCount = 5; + + private: + // Returns the maximum size in bytes of one seek entry. + uint64_t MaxEntrySize() const; + + // Seek entry id element list. + uint32_t seek_entry_id_[kSeekEntryCount]; + + // Seek entry pos element list. + uint64_t seek_entry_pos_[kSeekEntryCount]; + + // The file position of SeekHead element. + int64_t start_pos_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SeekHead); +}; + +/////////////////////////////////////////////////////////////// +// Segment Information element +class SegmentInfo { + public: + SegmentInfo(); + ~SegmentInfo(); + + // Will update the duration if |duration_| is > 0.0. Returns true on success. + bool Finalize(IMkvWriter* writer) const; + + // Sets |muxing_app_| and |writing_app_|. + bool Init(); + + // Output the Segment Information element to the writer. Returns true on + // success. + bool Write(IMkvWriter* writer); + + void set_duration(double duration) { duration_ = duration; } + double duration() const { return duration_; } + void set_muxing_app(const char* app); + const char* muxing_app() const { return muxing_app_; } + void set_timecode_scale(uint64_t scale) { timecode_scale_ = scale; } + uint64_t timecode_scale() const { return timecode_scale_; } + void set_writing_app(const char* app); + const char* writing_app() const { return writing_app_; } + void set_date_utc(int64_t date_utc) { date_utc_ = date_utc; } + int64_t date_utc() const { return date_utc_; } + + private: + // Segment Information element names. + // Initially set to -1 to signify that a duration has not been set and should + // not be written out. + double duration_; + // Set to libwebm-%d.%d.%d.%d, major, minor, build, revision. + char* muxing_app_; + uint64_t timecode_scale_; + // Initially set to libwebm-%d.%d.%d.%d, major, minor, build, revision. + char* writing_app_; + // LLONG_MIN when DateUTC is not set. + int64_t date_utc_; + + // The file position of the duration element. + int64_t duration_pos_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SegmentInfo); +}; + +/////////////////////////////////////////////////////////////// +// This class represents the main segment in a WebM file. Currently only +// supports one Segment element. +// +// Notes: +// |Init| must be called before any other method in this class. +class Segment { + public: + enum Mode { kLive = 0x1, kFile = 0x2 }; + + enum CuesPosition { + kAfterClusters = 0x0, // Position Cues after Clusters - Default + kBeforeClusters = 0x1 // Position Cues before Clusters + }; + + static const uint32_t kDefaultDocTypeVersion = 4; + static const uint64_t kDefaultMaxClusterDuration = 30000000000ULL; + + Segment(); + ~Segment(); + + // Initializes |SegmentInfo| and returns result. Always returns false when + // |ptr_writer| is NULL. + bool Init(IMkvWriter* ptr_writer); + + // Adds a generic track to the segment. Returns the newly-allocated + // track object (which is owned by the segment) on success, NULL on + // error. |number| is the number to use for the track. |number| + // must be >= 0. If |number| == 0 then the muxer will decide on the + // track number. + Track* AddTrack(int32_t number); + + // Adds a Vorbis audio track to the segment. Returns the number of the track + // on success, 0 on error. |number| is the number to use for the audio track. + // |number| must be >= 0. If |number| == 0 then the muxer will decide on + // the track number. + uint64_t AddAudioTrack(int32_t sample_rate, int32_t channels, int32_t number); + + // Adds an empty chapter to the chapters of this segment. Returns + // non-NULL on success. After adding the chapter, the caller should + // populate its fields via the Chapter member functions. + Chapter* AddChapter(); + + // Adds an empty tag to the tags of this segment. Returns + // non-NULL on success. After adding the tag, the caller should + // populate its fields via the Tag member functions. + Tag* AddTag(); + + // Adds a cue point to the Cues element. |timestamp| is the time in + // nanoseconds of the cue's time. |track| is the Track of the Cue. This + // function must be called after AddFrame to calculate the correct + // BlockNumber for the CuePoint. Returns true on success. + bool AddCuePoint(uint64_t timestamp, uint64_t track); + + // Adds a frame to be output in the file. Returns true on success. + // Inputs: + // data: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timestamp: Timestamp of the frame in nanoseconds from 0. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrame(const uint8_t* data, uint64_t length, uint64_t track_number, + uint64_t timestamp_ns, bool is_key); + + // Writes a frame of metadata to the output medium; returns true on + // success. + // Inputs: + // data: Pointer to the data + // length: Length of the data + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timecode: Absolute timestamp of the metadata frame, expressed + // in nanosecond units. + // duration: Duration of metadata frame, in nanosecond units. + // + // The metadata frame is written as a block group, with a duration + // sub-element but no reference time sub-elements (indicating that + // it is considered a keyframe, per Matroska semantics). + bool AddMetadata(const uint8_t* data, uint64_t length, uint64_t track_number, + uint64_t timestamp_ns, uint64_t duration_ns); + + // Writes a frame with additional data to the output medium; returns true on + // success. + // Inputs: + // data: Pointer to the data. + // length: Length of the data. + // additional: Pointer to additional data. + // additional_length: Length of additional data. + // add_id: Additional ID which identifies the type of additional data. + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timestamp: Absolute timestamp of the frame, expressed in nanosecond + // units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithAdditional(const uint8_t* data, uint64_t length, + const uint8_t* additional, + uint64_t additional_length, uint64_t add_id, + uint64_t track_number, uint64_t timestamp, + bool is_key); + + // Writes a frame with DiscardPadding to the output medium; returns true on + // success. + // Inputs: + // data: Pointer to the data. + // length: Length of the data. + // discard_padding: DiscardPadding element value. + // track_number: Track to add the data to. Value returned by Add track + // functions. + // timestamp: Absolute timestamp of the frame, expressed in nanosecond + // units. + // is_key: Flag telling whether or not this frame is a key frame. + bool AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length, + int64_t discard_padding, + uint64_t track_number, uint64_t timestamp, + bool is_key); + + // Writes a Frame to the output medium. Chooses the correct way of writing + // the frame (Block vs SimpleBlock) based on the parameters passed. + // Inputs: + // frame: frame object + bool AddGenericFrame(const Frame* frame); + + // Adds a VP8 video track to the segment. Returns the number of the track on + // success, 0 on error. |number| is the number to use for the video track. + // |number| must be >= 0. If |number| == 0 then the muxer will decide on + // the track number. + uint64_t AddVideoTrack(int32_t width, int32_t height, int32_t number); + + // This function must be called after Finalize() if you need a copy of the + // output with Cues written before the Clusters. It will return false if the + // writer is not seekable of if chunking is set to true. + // Input parameters: + // reader - an IMkvReader object created with the same underlying file of the + // current writer object. Make sure to close the existing writer + // object before creating this so that all the data is properly + // flushed and available for reading. + // writer - an IMkvWriter object pointing to a *different* file than the one + // pointed by the current writer object. This file will contain the + // Cues element before the Clusters. + bool CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader, + IMkvWriter* writer); + + // Sets which track to use for the Cues element. Must have added the track + // before calling this function. Returns true on success. |track_number| is + // returned by the Add track functions. + bool CuesTrack(uint64_t track_number); + + // This will force the muxer to create a new Cluster when the next frame is + // added. + void ForceNewClusterOnNextFrame(); + + // Writes out any frames that have not been written out. Finalizes the last + // cluster. May update the size and duration of the segment. May output the + // Cues element. May finalize the SeekHead element. Returns true on success. + bool Finalize(); + + // Returns the Cues object. + Cues* GetCues() { return &cues_; } + + // Returns the Segment Information object. + const SegmentInfo* GetSegmentInfo() const { return &segment_info_; } + SegmentInfo* GetSegmentInfo() { return &segment_info_; } + + // Search the Tracks and return the track that matches |track_number|. + // Returns NULL if there is no track match. + Track* GetTrackByNumber(uint64_t track_number) const; + + // Toggles whether to output a cues element. + void OutputCues(bool output_cues); + + // Toggles whether to write the last frame in each Cluster with Duration. + void AccurateClusterDuration(bool accurate_cluster_duration); + + // Toggles whether to write the Cluster Timecode using exactly 8 bytes. + void UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode); + + // Sets if the muxer will output files in chunks or not. |chunking| is a + // flag telling whether or not to turn on chunking. |filename| is the base + // filename for the chunk files. The header chunk file will be named + // |filename|.hdr and the data chunks will be named + // |filename|_XXXXXX.chk. Chunking implies that the muxer will be writing + // to files so the muxer will use the default MkvWriter class to control + // what data is written to what files. Returns true on success. + // TODO: Should we change the IMkvWriter Interface to add Open and Close? + // That will force the interface to be dependent on files. + bool SetChunking(bool chunking, const char* filename); + + bool chunking() const { return chunking_; } + uint64_t cues_track() const { return cues_track_; } + void set_max_cluster_duration(uint64_t max_cluster_duration) { + max_cluster_duration_ = max_cluster_duration; + } + uint64_t max_cluster_duration() const { return max_cluster_duration_; } + void set_max_cluster_size(uint64_t max_cluster_size) { + max_cluster_size_ = max_cluster_size; + } + uint64_t max_cluster_size() const { return max_cluster_size_; } + void set_mode(Mode mode) { mode_ = mode; } + Mode mode() const { return mode_; } + CuesPosition cues_position() const { return cues_position_; } + bool output_cues() const { return output_cues_; } + void set_estimate_file_duration(bool estimate_duration) { + estimate_file_duration_ = estimate_duration; + } + bool estimate_file_duration() const { return estimate_file_duration_; } + const SegmentInfo* segment_info() const { return &segment_info_; } + void set_duration(double duration) { duration_ = duration; } + double duration() const { return duration_; } + + // Returns true when codec IDs are valid for WebM. + bool DocTypeIsWebm() const; + + private: + // Checks if header information has been output and initialized. If not it + // will output the Segment element and initialize the SeekHead elment and + // Cues elements. + bool CheckHeaderInfo(); + + // Sets |doc_type_version_| based on the current element requirements. + void UpdateDocTypeVersion(); + + // Sets |name| according to how many chunks have been written. |ext| is the + // file extension. |name| must be deleted by the calling app. Returns true + // on success. + bool UpdateChunkName(const char* ext, char** name) const; + + // Returns the maximum offset within the segment's payload. When chunking + // this function is needed to determine offsets of elements within the + // chunked files. Returns -1 on error. + int64_t MaxOffset(); + + // Adds the frame to our frame array. + bool QueueFrame(Frame* frame); + + // Output all frames that are queued. Returns -1 on error, otherwise + // it returns the number of frames written. + int WriteFramesAll(); + + // Output all frames that are queued that have an end time that is less + // then |timestamp|. Returns true on success and if there are no frames + // queued. + bool WriteFramesLessThan(uint64_t timestamp); + + // Outputs the segment header, Segment Information element, SeekHead element, + // and Tracks element to |writer_|. + bool WriteSegmentHeader(); + + // Given a frame with the specified timestamp (nanosecond units) and + // keyframe status, determine whether a new cluster should be + // created, before writing enqueued frames and the frame itself. The + // function returns one of the following values: + // -1 = error: an out-of-order frame was detected + // 0 = do not create a new cluster, and write frame to the existing cluster + // 1 = create a new cluster, and write frame to that new cluster + // 2 = create a new cluster, and re-run test + int TestFrame(uint64_t track_num, uint64_t timestamp_ns, bool key) const; + + // Create a new cluster, using the earlier of the first enqueued + // frame, or the indicated time. Returns true on success. + bool MakeNewCluster(uint64_t timestamp_ns); + + // Checks whether a new cluster needs to be created, and if so + // creates a new cluster. Returns false if creation of a new cluster + // was necessary but creation was not successful. + bool DoNewClusterProcessing(uint64_t track_num, uint64_t timestamp_ns, + bool key); + + // Adjusts Cue Point values (to place Cues before Clusters) so that they + // reflect the correct offsets. + void MoveCuesBeforeClusters(); + + // This function recursively computes the correct cluster offsets (this is + // done to move the Cues before Clusters). It recursively updates the change + // in size (which indicates a change in cluster offset) until no sizes change. + // Parameters: + // diff - indicates the difference in size of the Cues element that needs to + // accounted for. + // index - index in the list of Cues which is currently being adjusted. + // cue_size - sum of size of all the CuePoint elements. + void MoveCuesBeforeClustersHelper(uint64_t diff, int index, + uint64_t* cue_size); + + // Seeds the random number generator used to make UIDs. + unsigned int seed_; + + // WebM elements + Cues cues_; + SeekHead seek_head_; + SegmentInfo segment_info_; + Tracks tracks_; + Chapters chapters_; + Tags tags_; + + // Number of chunks written. + int chunk_count_; + + // Current chunk filename. + char* chunk_name_; + + // Default MkvWriter object created by this class used for writing clusters + // out in separate files. + MkvWriter* chunk_writer_cluster_; + + // Default MkvWriter object created by this class used for writing Cues + // element out to a file. + MkvWriter* chunk_writer_cues_; + + // Default MkvWriter object created by this class used for writing the + // Matroska header out to a file. + MkvWriter* chunk_writer_header_; + + // Flag telling whether or not the muxer is chunking output to multiple + // files. + bool chunking_; + + // Base filename for the chunked files. + char* chunking_base_name_; + + // File position offset where the Clusters end. + int64_t cluster_end_offset_; + + // List of clusters. + Cluster** cluster_list_; + + // Number of cluster pointers allocated in the cluster list. + int32_t cluster_list_capacity_; + + // Number of clusters in the cluster list. + int32_t cluster_list_size_; + + // Indicates whether Cues should be written before or after Clusters + CuesPosition cues_position_; + + // Track number that is associated with the cues element for this segment. + uint64_t cues_track_; + + // Tells the muxer to force a new cluster on the next Block. + bool force_new_cluster_; + + // List of stored audio frames. These variables are used to store frames so + // the muxer can follow the guideline "Audio blocks that contain the video + // key frame's timecode should be in the same cluster as the video key frame + // block." + Frame** frames_; + + // Number of frame pointers allocated in the frame list. + int32_t frames_capacity_; + + // Number of frames in the frame list. + int32_t frames_size_; + + // Flag telling if a video track has been added to the segment. + bool has_video_; + + // Flag telling if the segment's header has been written. + bool header_written_; + + // Duration of the last block in nanoseconds. + uint64_t last_block_duration_; + + // Last timestamp in nanoseconds added to a cluster. + uint64_t last_timestamp_; + + // Last timestamp in nanoseconds by track number added to a cluster. + uint64_t last_track_timestamp_[kMaxTrackNumber]; + + // Number of frames written per track. + uint64_t track_frames_written_[kMaxTrackNumber]; + + // Maximum time in nanoseconds for a cluster duration. This variable is a + // guideline and some clusters may have a longer duration. Default is 30 + // seconds. + uint64_t max_cluster_duration_; + + // Maximum size in bytes for a cluster. This variable is a guideline and + // some clusters may have a larger size. Default is 0 which signifies that + // the muxer will decide the size. + uint64_t max_cluster_size_; + + // The mode that segment is in. If set to |kLive| the writer must not + // seek backwards. + Mode mode_; + + // Flag telling the muxer that a new cue point should be added. + bool new_cuepoint_; + + // TODO(fgalligan): Should we add support for more than one Cues element? + // Flag whether or not the muxer should output a Cues element. + bool output_cues_; + + // Flag whether or not the last frame in each Cluster will have a Duration + // element in it. + bool accurate_cluster_duration_; + + // Flag whether or not to write the Cluster Timecode using exactly 8 bytes. + bool fixed_size_cluster_timecode_; + + // Flag whether or not to estimate the file duration. + bool estimate_file_duration_; + + // The size of the EBML header, used to validate the header if + // WriteEbmlHeader() is called more than once. + int32_t ebml_header_size_; + + // The file position of the segment's payload. + int64_t payload_pos_; + + // The file position of the element's size. + int64_t size_position_; + + // Current DocTypeVersion (|doc_type_version_|) and that written in + // WriteSegmentHeader(). + // WriteEbmlHeader() will be called from Finalize() if |doc_type_version_| + // differs from |doc_type_version_written_|. + uint32_t doc_type_version_; + uint32_t doc_type_version_written_; + + // If |duration_| is > 0, then explicitly set the duration of the segment. + double duration_; + + // Pointer to the writer objects. Not owned by this class. + IMkvWriter* writer_cluster_; + IMkvWriter* writer_cues_; + IMkvWriter* writer_header_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Segment); +}; + +} // namespace mkvmuxer + +#endif // MKVMUXER_MKVMUXER_H_ diff --git a/mkvmuxer/mkvmuxertypes.h b/mkvmuxer/mkvmuxertypes.h new file mode 100644 index 0000000..e5db121 --- /dev/null +++ b/mkvmuxer/mkvmuxertypes.h @@ -0,0 +1,28 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef MKVMUXER_MKVMUXERTYPES_H_ +#define MKVMUXER_MKVMUXERTYPES_H_ + +namespace mkvmuxer { +typedef unsigned char uint8; +typedef short int16; +typedef int int32; +typedef unsigned int uint32; +typedef long long int64; +typedef unsigned long long uint64; +} // namespace mkvmuxer + +// Copied from Chromium basictypes.h +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define LIBWEBM_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +#endif // MKVMUXER_MKVMUXERTYPES_HPP_ diff --git a/mkvmuxer/mkvmuxerutil.cc b/mkvmuxer/mkvmuxerutil.cc new file mode 100644 index 0000000..6436817 --- /dev/null +++ b/mkvmuxer/mkvmuxerutil.cc @@ -0,0 +1,743 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "mkvmuxer/mkvmuxerutil.h" + +#ifdef __ANDROID__ +#include <fcntl.h> +#include <unistd.h> +#endif + +#include <cassert> +#include <cmath> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <new> + +#include "common/webmids.h" +#include "mkvmuxer/mkvmuxer.h" +#include "mkvmuxer/mkvwriter.h" + +namespace mkvmuxer { + +namespace { + +// Date elements are always 8 octets in size. +const int kDateElementSize = 8; + +uint64 WriteBlock(IMkvWriter* writer, const Frame* const frame, int64 timecode, + uint64 timecode_scale) { + uint64 block_additional_elem_size = 0; + uint64 block_addid_elem_size = 0; + uint64 block_more_payload_size = 0; + uint64 block_more_elem_size = 0; + uint64 block_additions_payload_size = 0; + uint64 block_additions_elem_size = 0; + if (frame->additional()) { + block_additional_elem_size = + EbmlElementSize(libwebm::kMkvBlockAdditional, frame->additional(), + frame->additional_length()); + block_addid_elem_size = EbmlElementSize( + libwebm::kMkvBlockAddID, static_cast<uint64>(frame->add_id())); + + block_more_payload_size = + block_addid_elem_size + block_additional_elem_size; + block_more_elem_size = + EbmlMasterElementSize(libwebm::kMkvBlockMore, block_more_payload_size) + + block_more_payload_size; + block_additions_payload_size = block_more_elem_size; + block_additions_elem_size = + EbmlMasterElementSize(libwebm::kMkvBlockAdditions, + block_additions_payload_size) + + block_additions_payload_size; + } + + uint64 discard_padding_elem_size = 0; + if (frame->discard_padding() != 0) { + discard_padding_elem_size = + EbmlElementSize(libwebm::kMkvDiscardPadding, + static_cast<int64>(frame->discard_padding())); + } + + const uint64 reference_block_timestamp = + frame->reference_block_timestamp() / timecode_scale; + uint64 reference_block_elem_size = 0; + if (!frame->is_key()) { + reference_block_elem_size = + EbmlElementSize(libwebm::kMkvReferenceBlock, reference_block_timestamp); + } + + const uint64 duration = frame->duration() / timecode_scale; + uint64 block_duration_elem_size = 0; + if (duration > 0) + block_duration_elem_size = + EbmlElementSize(libwebm::kMkvBlockDuration, duration); + + const uint64 block_payload_size = 4 + frame->length(); + const uint64 block_elem_size = + EbmlMasterElementSize(libwebm::kMkvBlock, block_payload_size) + + block_payload_size; + + const uint64 block_group_payload_size = + block_elem_size + block_additions_elem_size + block_duration_elem_size + + discard_padding_elem_size + reference_block_elem_size; + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockGroup, + block_group_payload_size)) { + return 0; + } + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlock, block_payload_size)) + return 0; + + if (WriteUInt(writer, frame->track_number())) + return 0; + + if (SerializeInt(writer, timecode, 2)) + return 0; + + // For a Block, flags is always 0. + if (SerializeInt(writer, 0, 1)) + return 0; + + if (writer->Write(frame->frame(), static_cast<uint32>(frame->length()))) + return 0; + + if (frame->additional()) { + if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockAdditions, + block_additions_payload_size)) { + return 0; + } + + if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockMore, + block_more_payload_size)) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvBlockAddID, + static_cast<uint64>(frame->add_id()))) + return 0; + + if (!WriteEbmlElement(writer, libwebm::kMkvBlockAdditional, + frame->additional(), frame->additional_length())) { + return 0; + } + } + + if (frame->discard_padding() != 0 && + !WriteEbmlElement(writer, libwebm::kMkvDiscardPadding, + static_cast<int64>(frame->discard_padding()))) { + return false; + } + + if (!frame->is_key() && !WriteEbmlElement(writer, libwebm::kMkvReferenceBlock, + reference_block_timestamp)) { + return false; + } + + if (duration > 0 && + !WriteEbmlElement(writer, libwebm::kMkvBlockDuration, duration)) { + return false; + } + return EbmlMasterElementSize(libwebm::kMkvBlockGroup, + block_group_payload_size) + + block_group_payload_size; +} + +uint64 WriteSimpleBlock(IMkvWriter* writer, const Frame* const frame, + int64 timecode) { + if (WriteID(writer, libwebm::kMkvSimpleBlock)) + return 0; + + const int32 size = static_cast<int32>(frame->length()) + 4; + if (WriteUInt(writer, size)) + return 0; + + if (WriteUInt(writer, static_cast<uint64>(frame->track_number()))) + return 0; + + if (SerializeInt(writer, timecode, 2)) + return 0; + + uint64 flags = 0; + if (frame->is_key()) + flags |= 0x80; + + if (SerializeInt(writer, flags, 1)) + return 0; + + if (writer->Write(frame->frame(), static_cast<uint32>(frame->length()))) + return 0; + + return GetUIntSize(libwebm::kMkvSimpleBlock) + GetCodedUIntSize(size) + 4 + + frame->length(); +} + +} // namespace + +int32 GetCodedUIntSize(uint64 value) { + if (value < 0x000000000000007FULL) + return 1; + else if (value < 0x0000000000003FFFULL) + return 2; + else if (value < 0x00000000001FFFFFULL) + return 3; + else if (value < 0x000000000FFFFFFFULL) + return 4; + else if (value < 0x00000007FFFFFFFFULL) + return 5; + else if (value < 0x000003FFFFFFFFFFULL) + return 6; + else if (value < 0x0001FFFFFFFFFFFFULL) + return 7; + return 8; +} + +int32 GetUIntSize(uint64 value) { + if (value < 0x0000000000000100ULL) + return 1; + else if (value < 0x0000000000010000ULL) + return 2; + else if (value < 0x0000000001000000ULL) + return 3; + else if (value < 0x0000000100000000ULL) + return 4; + else if (value < 0x0000010000000000ULL) + return 5; + else if (value < 0x0001000000000000ULL) + return 6; + else if (value < 0x0100000000000000ULL) + return 7; + return 8; +} + +int32 GetIntSize(int64 value) { + // Doubling the requested value ensures positive values with their high bit + // set are written with 0-padding to avoid flipping the signedness. + const uint64 v = (value < 0) ? value ^ -1LL : value; + return GetUIntSize(2 * v); +} + +uint64 EbmlMasterElementSize(uint64 type, uint64 value) { + // Size of EBML ID + int32 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += GetCodedUIntSize(value); + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, int64 value) { + // Size of EBML ID + int32 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += GetIntSize(value); + + // Size of Datasize + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, uint64 value) { + return EbmlElementSize(type, value, 0); +} + +uint64 EbmlElementSize(uint64 type, uint64 value, uint64 fixed_size) { + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += (fixed_size > 0) ? fixed_size : GetUIntSize(value); + + // Size of Datasize + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, float /* value */) { + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += sizeof(float); + + // Size of Datasize + ebml_size++; + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, const char* value) { + if (!value) + return 0; + + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += strlen(value); + + // Size of Datasize + ebml_size += GetCodedUIntSize(strlen(value)); + + return ebml_size; +} + +uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size) { + if (!value) + return 0; + + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += size; + + // Size of Datasize + ebml_size += GetCodedUIntSize(size); + + return ebml_size; +} + +uint64 EbmlDateElementSize(uint64 type) { + // Size of EBML ID + uint64 ebml_size = GetUIntSize(type); + + // Datasize + ebml_size += kDateElementSize; + + // Size of Datasize + ebml_size++; + + return ebml_size; +} + +int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size) { + if (!writer || size < 1 || size > 8) + return -1; + + for (int32 i = 1; i <= size; ++i) { + const int32 byte_count = size - i; + const int32 bit_count = byte_count * 8; + + const int64 bb = value >> bit_count; + const uint8 b = static_cast<uint8>(bb); + + const int32 status = writer->Write(&b, 1); + + if (status < 0) + return status; + } + + return 0; +} + +int32 SerializeFloat(IMkvWriter* writer, float f) { + if (!writer) + return -1; + + assert(sizeof(uint32) == sizeof(float)); + // This union is merely used to avoid a reinterpret_cast from float& to + // uint32& which will result in violation of strict aliasing. + union U32 { + uint32 u32; + float f; + } value; + value.f = f; + + for (int32 i = 1; i <= 4; ++i) { + const int32 byte_count = 4 - i; + const int32 bit_count = byte_count * 8; + + const uint8 byte = static_cast<uint8>(value.u32 >> bit_count); + + const int32 status = writer->Write(&byte, 1); + + if (status < 0) + return status; + } + + return 0; +} + +int32 WriteUInt(IMkvWriter* writer, uint64 value) { + if (!writer) + return -1; + + int32 size = GetCodedUIntSize(value); + + return WriteUIntSize(writer, value, size); +} + +int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size) { + if (!writer || size < 0 || size > 8) + return -1; + + if (size > 0) { + const uint64 bit = 1LL << (size * 7); + + if (value > (bit - 2)) + return -1; + + value |= bit; + } else { + size = 1; + int64 bit; + + for (;;) { + bit = 1LL << (size * 7); + const uint64 max = bit - 2; + + if (value <= max) + break; + + ++size; + } + + if (size > 8) + return false; + + value |= bit; + } + + return SerializeInt(writer, value, size); +} + +int32 WriteID(IMkvWriter* writer, uint64 type) { + if (!writer) + return -1; + + writer->ElementStartNotify(type, writer->Position()); + + const int32 size = GetUIntSize(type); + + return SerializeInt(writer, type, size); +} + +bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 type, uint64 size) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, size)) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value) { + return WriteEbmlElement(writer, type, value, 0); +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value, + uint64 fixed_size) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return false; + + uint64 size = GetUIntSize(value); + if (fixed_size > 0) { + if (size > fixed_size) + return false; + size = fixed_size; + } + if (WriteUInt(writer, size)) + return false; + + if (SerializeInt(writer, value, static_cast<int32>(size))) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, int64 value) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return 0; + + const uint64 size = GetIntSize(value); + if (WriteUInt(writer, size)) + return false; + + if (SerializeInt(writer, value, static_cast<int32>(size))) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, 4)) + return false; + + if (SerializeFloat(writer, value)) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value) { + if (!writer || !value) + return false; + + if (WriteID(writer, type)) + return false; + + const uint64 length = strlen(value); + if (WriteUInt(writer, length)) + return false; + + if (writer->Write(value, static_cast<uint32>(length))) + return false; + + return true; +} + +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const uint8* value, + uint64 size) { + if (!writer || !value || size < 1) + return false; + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, size)) + return false; + + if (writer->Write(value, static_cast<uint32>(size))) + return false; + + return true; +} + +bool WriteEbmlDateElement(IMkvWriter* writer, uint64 type, int64 value) { + if (!writer) + return false; + + if (WriteID(writer, type)) + return false; + + if (WriteUInt(writer, kDateElementSize)) + return false; + + if (SerializeInt(writer, value, kDateElementSize)) + return false; + + return true; +} + +uint64 WriteFrame(IMkvWriter* writer, const Frame* const frame, + Cluster* cluster) { + if (!writer || !frame || !frame->IsValid() || !cluster || + !cluster->timecode_scale()) + return 0; + + // Technically the timecode for a block can be less than the + // timecode for the cluster itself (remember that block timecode + // is a signed, 16-bit integer). However, as a simplification we + // only permit non-negative cluster-relative timecodes for blocks. + const int64 relative_timecode = cluster->GetRelativeTimecode( + frame->timestamp() / cluster->timecode_scale()); + if (relative_timecode < 0 || relative_timecode > kMaxBlockTimecode) + return 0; + + return frame->CanBeSimpleBlock() + ? WriteSimpleBlock(writer, frame, relative_timecode) + : WriteBlock(writer, frame, relative_timecode, + cluster->timecode_scale()); +} + +uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) { + if (!writer) + return false; + + // Subtract one for the void ID and the coded size. + uint64 void_entry_size = size - 1 - GetCodedUIntSize(size - 1); + uint64 void_size = EbmlMasterElementSize(libwebm::kMkvVoid, void_entry_size) + + void_entry_size; + + if (void_size != size) + return 0; + + const int64 payload_position = writer->Position(); + if (payload_position < 0) + return 0; + + if (WriteID(writer, libwebm::kMkvVoid)) + return 0; + + if (WriteUInt(writer, void_entry_size)) + return 0; + + const uint8 value = 0; + for (int32 i = 0; i < static_cast<int32>(void_entry_size); ++i) { + if (writer->Write(&value, 1)) + return 0; + } + + const int64 stop_position = writer->Position(); + if (stop_position < 0 || + stop_position - payload_position != static_cast<int64>(void_size)) + return 0; + + return void_size; +} + +void GetVersion(int32* major, int32* minor, int32* build, int32* revision) { + *major = 0; + *minor = 2; + *build = 1; + *revision = 0; +} + +uint64 MakeUID(unsigned int* seed) { + uint64 uid = 0; + +#ifdef __MINGW32__ + srand(*seed); +#endif + + for (int i = 0; i < 7; ++i) { // avoid problems with 8-byte values + uid <<= 8; + +// TODO(fgalligan): Move random number generation to platform specific code. +#ifdef _MSC_VER + (void)seed; + const int32 nn = rand(); +#elif __ANDROID__ + (void)seed; + int32 temp_num = 1; + int fd = open("/dev/urandom", O_RDONLY); + if (fd != -1) { + read(fd, &temp_num, sizeof(temp_num)); + close(fd); + } + const int32 nn = temp_num; +#elif defined __MINGW32__ + const int32 nn = rand(); +#else + const int32 nn = rand_r(seed); +#endif + const int32 n = 0xFF & (nn >> 4); // throw away low-order bits + + uid |= n; + } + + return uid; +} + +bool IsMatrixCoefficientsValueValid(uint64_t value) { + switch (value) { + case mkvmuxer::Colour::kGbr: + case mkvmuxer::Colour::kBt709: + case mkvmuxer::Colour::kUnspecifiedMc: + case mkvmuxer::Colour::kReserved: + case mkvmuxer::Colour::kFcc: + case mkvmuxer::Colour::kBt470bg: + case mkvmuxer::Colour::kSmpte170MMc: + case mkvmuxer::Colour::kSmpte240MMc: + case mkvmuxer::Colour::kYcocg: + case mkvmuxer::Colour::kBt2020NonConstantLuminance: + case mkvmuxer::Colour::kBt2020ConstantLuminance: + return true; + } + return false; +} + +bool IsChromaSitingHorzValueValid(uint64_t value) { + switch (value) { + case mkvmuxer::Colour::kUnspecifiedCsh: + case mkvmuxer::Colour::kLeftCollocated: + case mkvmuxer::Colour::kHalfCsh: + return true; + } + return false; +} + +bool IsChromaSitingVertValueValid(uint64_t value) { + switch (value) { + case mkvmuxer::Colour::kUnspecifiedCsv: + case mkvmuxer::Colour::kTopCollocated: + case mkvmuxer::Colour::kHalfCsv: + return true; + } + return false; +} + +bool IsColourRangeValueValid(uint64_t value) { + switch (value) { + case mkvmuxer::Colour::kUnspecifiedCr: + case mkvmuxer::Colour::kBroadcastRange: + case mkvmuxer::Colour::kFullRange: + case mkvmuxer::Colour::kMcTcDefined: + return true; + } + return false; +} + +bool IsTransferCharacteristicsValueValid(uint64_t value) { + switch (value) { + case mkvmuxer::Colour::kIturBt709Tc: + case mkvmuxer::Colour::kUnspecifiedTc: + case mkvmuxer::Colour::kReservedTc: + case mkvmuxer::Colour::kGamma22Curve: + case mkvmuxer::Colour::kGamma28Curve: + case mkvmuxer::Colour::kSmpte170MTc: + case mkvmuxer::Colour::kSmpte240MTc: + case mkvmuxer::Colour::kLinear: + case mkvmuxer::Colour::kLog: + case mkvmuxer::Colour::kLogSqrt: + case mkvmuxer::Colour::kIec6196624: + case mkvmuxer::Colour::kIturBt1361ExtendedColourGamut: + case mkvmuxer::Colour::kIec6196621: + case mkvmuxer::Colour::kIturBt202010bit: + case mkvmuxer::Colour::kIturBt202012bit: + case mkvmuxer::Colour::kSmpteSt2084: + case mkvmuxer::Colour::kSmpteSt4281Tc: + case mkvmuxer::Colour::kAribStdB67Hlg: + return true; + } + return false; +} + +bool IsPrimariesValueValid(uint64_t value) { + switch (value) { + case mkvmuxer::Colour::kReservedP0: + case mkvmuxer::Colour::kIturBt709P: + case mkvmuxer::Colour::kUnspecifiedP: + case mkvmuxer::Colour::kReservedP3: + case mkvmuxer::Colour::kIturBt470M: + case mkvmuxer::Colour::kIturBt470Bg: + case mkvmuxer::Colour::kSmpte170MP: + case mkvmuxer::Colour::kSmpte240MP: + case mkvmuxer::Colour::kFilm: + case mkvmuxer::Colour::kIturBt2020: + case mkvmuxer::Colour::kSmpteSt4281P: + case mkvmuxer::Colour::kJedecP22Phosphors: + return true; + } + return false; +} + +} // namespace mkvmuxer diff --git a/mkvmuxer/mkvmuxerutil.h b/mkvmuxer/mkvmuxerutil.h new file mode 100644 index 0000000..3355428 --- /dev/null +++ b/mkvmuxer/mkvmuxerutil.h @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef MKVMUXER_MKVMUXERUTIL_H_ +#define MKVMUXER_MKVMUXERUTIL_H_ + +#include "mkvmuxertypes.h" + +#include "stdint.h" + +namespace mkvmuxer { +class Cluster; +class Frame; +class IMkvWriter; + +// TODO(tomfinegan): mkvmuxer:: integer types continue to be used here because +// changing them causes pain for downstream projects. It would be nice if a +// solution that allows removal of the mkvmuxer:: integer types while avoiding +// pain for downstream users of libwebm. Considering that mkvmuxerutil.{cc,h} +// are really, for the great majority of cases, EBML size calculation and writer +// functions, perhaps a more EBML focused utility would be the way to go as a +// first step. + +const uint64 kEbmlUnknownValue = 0x01FFFFFFFFFFFFFFULL; +const int64 kMaxBlockTimecode = 0x07FFFLL; + +// Writes out |value| in Big Endian order. Returns 0 on success. +int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size); + +// Writes out |f| in Big Endian order. Returns 0 on success. +int32 SerializeFloat(IMkvWriter* writer, float f); + +// Returns the size in bytes of the element. +int32 GetUIntSize(uint64 value); +int32 GetIntSize(int64 value); +int32 GetCodedUIntSize(uint64 value); +uint64 EbmlMasterElementSize(uint64 type, uint64 value); +uint64 EbmlElementSize(uint64 type, int64 value); +uint64 EbmlElementSize(uint64 type, uint64 value); +uint64 EbmlElementSize(uint64 type, float value); +uint64 EbmlElementSize(uint64 type, const char* value); +uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size); +uint64 EbmlDateElementSize(uint64 type); + +// Returns the size in bytes of the element assuming that the element was +// written using |fixed_size| bytes. If |fixed_size| is set to zero, then it +// computes the necessary number of bytes based on |value|. +uint64 EbmlElementSize(uint64 type, uint64 value, uint64 fixed_size); + +// Creates an EBML coded number from |value| and writes it out. The size of +// the coded number is determined by the value of |value|. |value| must not +// be in a coded form. Returns 0 on success. +int32 WriteUInt(IMkvWriter* writer, uint64 value); + +// Creates an EBML coded number from |value| and writes it out. The size of +// the coded number is determined by the value of |size|. |value| must not +// be in a coded form. Returns 0 on success. +int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size); + +// Output an Mkv master element. Returns true if the element was written. +bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 value, uint64 size); + +// Outputs an Mkv ID, calls |IMkvWriter::ElementStartNotify|, and passes the +// ID to |SerializeInt|. Returns 0 on success. +int32 WriteID(IMkvWriter* writer, uint64 type); + +// Output an Mkv non-master element. Returns true if the element was written. +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value); +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, int64 value); +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value); +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value); +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const uint8* value, + uint64 size); +bool WriteEbmlDateElement(IMkvWriter* writer, uint64 type, int64 value); + +// Output an Mkv non-master element using fixed size. The element will be +// written out using exactly |fixed_size| bytes. If |fixed_size| is set to zero +// then it computes the necessary number of bytes based on |value|. Returns true +// if the element was written. +bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value, + uint64 fixed_size); + +// Output a Mkv Frame. It decides the correct element to write (Block vs +// SimpleBlock) based on the parameters of the Frame. +uint64 WriteFrame(IMkvWriter* writer, const Frame* const frame, + Cluster* cluster); + +// Output a void element. |size| must be the entire size in bytes that will be +// void. The function will calculate the size of the void header and subtract +// it from |size|. +uint64 WriteVoidElement(IMkvWriter* writer, uint64 size); + +// Returns the version number of the muxer in |major|, |minor|, |build|, +// and |revision|. +void GetVersion(int32* major, int32* minor, int32* build, int32* revision); + +// Returns a random number to be used for UID, using |seed| to seed +// the random-number generator (see POSIX rand_r() for semantics). +uint64 MakeUID(unsigned int* seed); + +// Colour field validation helpers. All return true when |value| is valid. +bool IsMatrixCoefficientsValueValid(uint64_t value); +bool IsChromaSitingHorzValueValid(uint64_t value); +bool IsChromaSitingVertValueValid(uint64_t value); +bool IsColourRangeValueValid(uint64_t value); +bool IsTransferCharacteristicsValueValid(uint64_t value); +bool IsPrimariesValueValid(uint64_t value); + +} // namespace mkvmuxer + +#endif // MKVMUXER_MKVMUXERUTIL_H_ diff --git a/mkvmuxer/mkvwriter.cc b/mkvmuxer/mkvwriter.cc new file mode 100644 index 0000000..d668384 --- /dev/null +++ b/mkvmuxer/mkvwriter.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "mkvmuxer/mkvwriter.h" + +#include <sys/types.h> + +#ifdef _MSC_VER +#include <share.h> // for _SH_DENYWR +#endif + +namespace mkvmuxer { + +MkvWriter::MkvWriter() : file_(NULL), writer_owns_file_(true) {} + +MkvWriter::MkvWriter(FILE* fp) : file_(fp), writer_owns_file_(false) {} + +MkvWriter::~MkvWriter() { Close(); } + +int32 MkvWriter::Write(const void* buffer, uint32 length) { + if (!file_) + return -1; + + if (length == 0) + return 0; + + if (buffer == NULL) + return -1; + + const size_t bytes_written = fwrite(buffer, 1, length, file_); + + return (bytes_written == length) ? 0 : -1; +} + +bool MkvWriter::Open(const char* filename) { + if (filename == NULL) + return false; + + if (file_) + return false; + +#ifdef _MSC_VER + file_ = _fsopen(filename, "wb", _SH_DENYWR); +#else + file_ = fopen(filename, "wb"); +#endif + if (file_ == NULL) + return false; + return true; +} + +void MkvWriter::Close() { + if (file_ && writer_owns_file_) { + fclose(file_); + } + file_ = NULL; +} + +int64 MkvWriter::Position() const { + if (!file_) + return 0; + +#ifdef _MSC_VER + return _ftelli64(file_); +#else + return ftell(file_); +#endif +} + +int32 MkvWriter::Position(int64 position) { + if (!file_) + return -1; + +#ifdef _MSC_VER + return _fseeki64(file_, position, SEEK_SET); +#elif defined(_WIN32) + return fseeko64(file_, static_cast<off_t>(position), SEEK_SET); +#else + return fseeko(file_, static_cast<off_t>(position), SEEK_SET); +#endif +} + +bool MkvWriter::Seekable() const { return true; } + +void MkvWriter::ElementStartNotify(uint64, int64) {} + +} // namespace mkvmuxer diff --git a/mkvmuxer/mkvwriter.h b/mkvmuxer/mkvwriter.h new file mode 100644 index 0000000..4227c63 --- /dev/null +++ b/mkvmuxer/mkvwriter.h @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef MKVMUXER_MKVWRITER_H_ +#define MKVMUXER_MKVWRITER_H_ + +#include <stdio.h> + +#include "mkvmuxer/mkvmuxer.h" +#include "mkvmuxer/mkvmuxertypes.h" + +namespace mkvmuxer { + +// Default implementation of the IMkvWriter interface on Windows. +class MkvWriter : public IMkvWriter { + public: + MkvWriter(); + explicit MkvWriter(FILE* fp); + virtual ~MkvWriter(); + + // IMkvWriter interface + virtual int64 Position() const; + virtual int32 Position(int64 position); + virtual bool Seekable() const; + virtual int32 Write(const void* buffer, uint32 length); + virtual void ElementStartNotify(uint64 element_id, int64 position); + + // Creates and opens a file for writing. |filename| is the name of the file + // to open. This function will overwrite the contents of |filename|. Returns + // true on success. + bool Open(const char* filename); + + // Closes an opened file. + void Close(); + + private: + // File handle to output file. + FILE* file_; + bool writer_owns_file_; + + LIBWEBM_DISALLOW_COPY_AND_ASSIGN(MkvWriter); +}; + +} // namespace mkvmuxer + +#endif // MKVMUXER_MKVWRITER_H_ diff --git a/mkvmuxer_sample.cc b/mkvmuxer_sample.cc new file mode 100644 index 0000000..9ef5569 --- /dev/null +++ b/mkvmuxer_sample.cc @@ -0,0 +1,802 @@ +// Copyright (c) 2011 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include <stdint.h> + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <list> +#include <memory> +#include <string> + +// libwebm common includes. +#include "common/file_util.h" +#include "common/hdr_util.h" + +// libwebm mkvparser includes +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" + +// libwebm mkvmuxer includes +#include "mkvmuxer/mkvmuxer.h" +#include "mkvmuxer/mkvmuxertypes.h" +#include "mkvmuxer/mkvwriter.h" + +#include "sample_muxer_metadata.h" + +namespace { + +void Usage() { + printf("Usage: mkvmuxer_sample -i input -o output [options]\n"); + printf("\n"); + printf("Main options:\n"); + printf(" -h | -? show help\n"); + printf(" -video <int> >0 outputs video\n"); + printf(" -audio <int> >0 outputs audio\n"); + printf(" -live <int> >0 puts the muxer into live mode\n"); + printf(" 0 puts the muxer into file mode\n"); + printf(" -output_cues <int> >0 outputs cues element\n"); + printf(" -cues_on_video_track <int> >0 outputs cues on video track\n"); + printf(" -cues_on_audio_track <int> >0 outputs cues on audio track\n"); + printf(" -max_cluster_duration <double> in seconds\n"); + printf(" -max_cluster_size <int> in bytes\n"); + printf(" -switch_tracks <int> >0 switches tracks in output\n"); + printf(" -audio_track_number <int> >0 Changes the audio track number\n"); + printf(" -video_track_number <int> >0 Changes the video track number\n"); + printf(" -chunking <string> Chunk output\n"); + printf(" -copy_tags <int> >0 Copies the tags\n"); + printf(" -accurate_cluster_duration <int> "); + printf(">0 Writes the last frame in each cluster with Duration\n"); + printf(" -fixed_size_cluster_timecode <int> "); + printf(">0 Writes the cluster timecode using exactly 8 bytes\n"); + printf(" -copy_input_duration >0 Copies the input duration\n"); + printf("\n"); + printf("Video options:\n"); + printf(" -display_width <int> Display width in pixels\n"); + printf(" -display_height <int> Display height in pixels\n"); + printf(" -pixel_width <int> Override pixel width\n"); + printf(" -pixel_height <int> Override pixel height\n"); + printf(" -projection_type <int> Set/override projection type:\n"); + printf(" 0: Rectangular\n"); + printf(" 1: Equirectangular\n"); + printf(" 2: Cube map\n"); + printf(" 3: Mesh\n"); + printf(" -projection_file <string> Override projection private data"); + printf(" with contents of this file\n"); + printf(" -projection_pose_yaw <float> Projection pose yaw\n"); + printf(" -projection_pose_pitch <float> Projection pose pitch\n"); + printf(" -projection_pose_roll <float> Projection pose roll\n"); + printf(" -stereo_mode <int> 3D video mode\n"); + printf("\n"); + printf("VP9 options:\n"); + printf(" -profile <int> VP9 profile\n"); + printf(" -level <int> VP9 level\n"); + printf("\n"); + printf("Cues options:\n"); + printf(" -output_cues_block_number <int> >0 outputs cue block number\n"); + printf(" -cues_before_clusters <int> >0 puts Cues before Clusters\n"); + printf("\n"); + printf("Metadata options:\n"); + printf(" -webvtt-subtitles <vttfile> "); + printf("add WebVTT subtitles as metadata track\n"); + printf(" -webvtt-captions <vttfile> "); + printf("add WebVTT captions as metadata track\n"); + printf(" -webvtt-descriptions <vttfile> "); + printf("add WebVTT descriptions as metadata track\n"); + printf(" -webvtt-metadata <vttfile> "); + printf("add WebVTT subtitles as metadata track\n"); + printf(" -webvtt-chapters <vttfile> "); + printf("add WebVTT chapters as MKV chapters element\n"); +} + +struct MetadataFile { + const char* name; + SampleMuxerMetadata::Kind kind; +}; + +typedef std::list<MetadataFile> metadata_files_t; + +// Cache the WebVTT filenames specified as command-line args. +bool LoadMetadataFiles(const metadata_files_t& files, + SampleMuxerMetadata* metadata) { + typedef metadata_files_t::const_iterator iter_t; + + iter_t i = files.begin(); + const iter_t j = files.end(); + + while (i != j) { + const metadata_files_t::value_type& v = *i++; + + if (!metadata->Load(v.name, v.kind)) + return false; + } + + return true; +} + +int ParseArgWebVTT(char* argv[], int* argv_index, int argc_check, + metadata_files_t* metadata_files) { + int& i = *argv_index; + + enum { kCount = 5 }; + struct Arg { + const char* name; + SampleMuxerMetadata::Kind kind; + }; + const Arg args[kCount] = { + {"-webvtt-subtitles", SampleMuxerMetadata::kSubtitles}, + {"-webvtt-captions", SampleMuxerMetadata::kCaptions}, + {"-webvtt-descriptions", SampleMuxerMetadata::kDescriptions}, + {"-webvtt-metadata", SampleMuxerMetadata::kMetadata}, + {"-webvtt-chapters", SampleMuxerMetadata::kChapters}}; + + for (int idx = 0; idx < kCount; ++idx) { + const Arg& arg = args[idx]; + + if (strcmp(arg.name, argv[i]) != 0) // no match + continue; + + ++i; // consume arg name here + + if (i > argc_check) { + printf("missing value for %s\n", arg.name); + return -1; // error + } + + MetadataFile f; + f.name = argv[i]; // arg value is consumed via caller's loop idx + f.kind = arg.kind; + + metadata_files->push_back(f); + return 1; // successfully parsed WebVTT arg + } + + return 0; // not a WebVTT arg +} + +bool CopyVideoProjection(const mkvparser::Projection& parser_projection, + mkvmuxer::Projection* muxer_projection) { + typedef mkvmuxer::Projection::ProjectionType MuxerProjType; + const int kTypeNotPresent = mkvparser::Projection::kTypeNotPresent; + if (parser_projection.type != kTypeNotPresent) { + muxer_projection->set_type( + static_cast<MuxerProjType>(parser_projection.type)); + } + if (parser_projection.private_data && + parser_projection.private_data_length > 0) { + if (!muxer_projection->SetProjectionPrivate( + parser_projection.private_data, + parser_projection.private_data_length)) { + return false; + } + } + + const float kValueNotPresent = mkvparser::Projection::kValueNotPresent; + if (parser_projection.pose_yaw != kValueNotPresent) + muxer_projection->set_pose_yaw(parser_projection.pose_yaw); + if (parser_projection.pose_pitch != kValueNotPresent) + muxer_projection->set_pose_pitch(parser_projection.pose_pitch); + if (parser_projection.pose_roll != kValueNotPresent) + muxer_projection->set_pose_roll(parser_projection.pose_roll); + return true; +} +} // end namespace + +int main(int argc, char* argv[]) { + char* input = NULL; + char* output = NULL; + + // Segment variables + bool output_video = true; + bool output_audio = true; + bool live_mode = false; + bool output_cues = true; + bool cues_before_clusters = false; + bool cues_on_video_track = true; + bool cues_on_audio_track = false; + uint64_t max_cluster_duration = 0; + uint64_t max_cluster_size = 0; + bool switch_tracks = false; + int audio_track_number = 0; // 0 tells muxer to decide. + int video_track_number = 0; // 0 tells muxer to decide. + bool chunking = false; + bool copy_tags = false; + const char* chunk_name = NULL; + bool accurate_cluster_duration = false; + bool fixed_size_cluster_timecode = false; + bool copy_input_duration = false; + + bool output_cues_block_number = true; + + uint64_t display_width = 0; + uint64_t display_height = 0; + uint64_t pixel_width = 0; + uint64_t pixel_height = 0; + uint64_t stereo_mode = 0; + const char* projection_file = 0; + int64_t projection_type = mkvparser::Projection::kTypeNotPresent; + float projection_pose_roll = mkvparser::Projection::kValueNotPresent; + float projection_pose_pitch = mkvparser::Projection::kValueNotPresent; + float projection_pose_yaw = mkvparser::Projection::kValueNotPresent; + int vp9_profile = -1; // No profile set. + int vp9_level = -1; // No level set. + + metadata_files_t metadata_files; + + const int argc_check = argc - 1; + for (int i = 1; i < argc; ++i) { + char* end; + + if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) { + Usage(); + return EXIT_SUCCESS; + } else if (!strcmp("-i", argv[i]) && i < argc_check) { + input = argv[++i]; + } else if (!strcmp("-o", argv[i]) && i < argc_check) { + output = argv[++i]; + } else if (!strcmp("-video", argv[i]) && i < argc_check) { + output_video = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-audio", argv[i]) && i < argc_check) { + output_audio = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-live", argv[i]) && i < argc_check) { + live_mode = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-output_cues", argv[i]) && i < argc_check) { + output_cues = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-cues_before_clusters", argv[i]) && i < argc_check) { + cues_before_clusters = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-cues_on_video_track", argv[i]) && i < argc_check) { + cues_on_video_track = strtol(argv[++i], &end, 10) == 0 ? false : true; + if (cues_on_video_track) + cues_on_audio_track = false; + } else if (!strcmp("-cues_on_audio_track", argv[i]) && i < argc_check) { + cues_on_audio_track = strtol(argv[++i], &end, 10) == 0 ? false : true; + if (cues_on_audio_track) + cues_on_video_track = false; + } else if (!strcmp("-max_cluster_duration", argv[i]) && i < argc_check) { + const double seconds = strtod(argv[++i], &end); + max_cluster_duration = static_cast<uint64_t>(seconds * 1000000000.0); + } else if (!strcmp("-max_cluster_size", argv[i]) && i < argc_check) { + max_cluster_size = strtol(argv[++i], &end, 10); + } else if (!strcmp("-switch_tracks", argv[i]) && i < argc_check) { + switch_tracks = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-audio_track_number", argv[i]) && i < argc_check) { + audio_track_number = static_cast<int>(strtol(argv[++i], &end, 10)); + } else if (!strcmp("-video_track_number", argv[i]) && i < argc_check) { + video_track_number = static_cast<int>(strtol(argv[++i], &end, 10)); + } else if (!strcmp("-chunking", argv[i]) && i < argc_check) { + chunking = true; + chunk_name = argv[++i]; + } else if (!strcmp("-copy_tags", argv[i]) && i < argc_check) { + copy_tags = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-accurate_cluster_duration", argv[i]) && + i < argc_check) { + accurate_cluster_duration = + strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-fixed_size_cluster_timecode", argv[i]) && + i < argc_check) { + fixed_size_cluster_timecode = + strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-copy_input_duration", argv[i]) && i < argc_check) { + copy_input_duration = strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (!strcmp("-display_width", argv[i]) && i < argc_check) { + display_width = strtol(argv[++i], &end, 10); + } else if (!strcmp("-display_height", argv[i]) && i < argc_check) { + display_height = strtol(argv[++i], &end, 10); + } else if (!strcmp("-pixel_width", argv[i]) && i < argc_check) { + pixel_width = strtol(argv[++i], &end, 10); + } else if (!strcmp("-pixel_height", argv[i]) && i < argc_check) { + pixel_height = strtol(argv[++i], &end, 10); + } else if (!strcmp("-stereo_mode", argv[i]) && i < argc_check) { + stereo_mode = strtol(argv[++i], &end, 10); + } else if (!strcmp("-projection_type", argv[i]) && i < argc_check) { + projection_type = strtol(argv[++i], &end, 10); + } else if (!strcmp("-projection_file", argv[i]) && i < argc_check) { + projection_file = argv[++i]; + } else if (!strcmp("-projection_pose_roll", argv[i]) && i < argc_check) { + projection_pose_roll = strtof(argv[++i], &end); + } else if (!strcmp("-projection_pose_pitch", argv[i]) && i < argc_check) { + projection_pose_pitch = strtof(argv[++i], &end); + } else if (!strcmp("-projection_pose_yaw", argv[i]) && i < argc_check) { + projection_pose_yaw = strtof(argv[++i], &end); + } else if (!strcmp("-profile", argv[i]) && i < argc_check) { + vp9_profile = static_cast<int>(strtol(argv[++i], &end, 10)); + } else if (!strcmp("-level", argv[i]) && i < argc_check) { + vp9_level = static_cast<int>(strtol(argv[++i], &end, 10)); + } else if (!strcmp("-output_cues_block_number", argv[i]) && + i < argc_check) { + output_cues_block_number = + strtol(argv[++i], &end, 10) == 0 ? false : true; + } else if (int e = ParseArgWebVTT(argv, &i, argc_check, &metadata_files)) { + if (e < 0) + return EXIT_FAILURE; + } + } + + if (input == NULL || output == NULL) { + Usage(); + return EXIT_FAILURE; + } + + // Get parser header info + mkvparser::MkvReader reader; + + if (reader.Open(input)) { + printf("\n Filename is invalid or error while opening.\n"); + return EXIT_FAILURE; + } + + long long pos = 0; + mkvparser::EBMLHeader ebml_header; + long long ret = ebml_header.Parse(&reader, pos); + if (ret) { + printf("\n EBMLHeader::Parse() failed."); + return EXIT_FAILURE; + } + + mkvparser::Segment* parser_segment_; + ret = mkvparser::Segment::CreateInstance(&reader, pos, parser_segment_); + if (ret) { + printf("\n Segment::CreateInstance() failed."); + return EXIT_FAILURE; + } + + const std::unique_ptr<mkvparser::Segment> parser_segment(parser_segment_); + ret = parser_segment->Load(); + if (ret < 0) { + printf("\n Segment::Load() failed."); + return EXIT_FAILURE; + } + + const mkvparser::SegmentInfo* const segment_info = parser_segment->GetInfo(); + if (segment_info == NULL) { + printf("\n Segment::GetInfo() failed."); + return EXIT_FAILURE; + } + const long long timeCodeScale = segment_info->GetTimeCodeScale(); + + // Set muxer header info + mkvmuxer::MkvWriter writer; + + const std::string temp_file = + cues_before_clusters ? libwebm::GetTempFileName() : output; + if (!writer.Open(temp_file.c_str())) { + printf("\n Filename is invalid or error while opening.\n"); + return EXIT_FAILURE; + } + + // Set Segment element attributes + mkvmuxer::Segment muxer_segment; + + if (!muxer_segment.Init(&writer)) { + printf("\n Could not initialize muxer segment!\n"); + return EXIT_FAILURE; + } + + muxer_segment.AccurateClusterDuration(accurate_cluster_duration); + muxer_segment.UseFixedSizeClusterTimecode(fixed_size_cluster_timecode); + + if (live_mode) + muxer_segment.set_mode(mkvmuxer::Segment::kLive); + else + muxer_segment.set_mode(mkvmuxer::Segment::kFile); + + if (chunking) + muxer_segment.SetChunking(true, chunk_name); + + if (max_cluster_duration > 0) + muxer_segment.set_max_cluster_duration(max_cluster_duration); + if (max_cluster_size > 0) + muxer_segment.set_max_cluster_size(max_cluster_size); + muxer_segment.OutputCues(output_cues); + + // Set SegmentInfo element attributes + mkvmuxer::SegmentInfo* const info = muxer_segment.GetSegmentInfo(); + info->set_timecode_scale(timeCodeScale); + info->set_writing_app("mkvmuxer_sample"); + + const mkvparser::Tags* const tags = parser_segment->GetTags(); + if (copy_tags && tags) { + for (int i = 0; i < tags->GetTagCount(); i++) { + const mkvparser::Tags::Tag* const tag = tags->GetTag(i); + mkvmuxer::Tag* muxer_tag = muxer_segment.AddTag(); + + for (int j = 0; j < tag->GetSimpleTagCount(); j++) { + const mkvparser::Tags::SimpleTag* const simple_tag = + tag->GetSimpleTag(j); + muxer_tag->add_simple_tag(simple_tag->GetTagName(), + simple_tag->GetTagString()); + } + } + } + + // Set Tracks element attributes + const mkvparser::Tracks* const parser_tracks = parser_segment->GetTracks(); + unsigned long i = 0; + uint64_t vid_track = 0; // no track added + uint64_t aud_track = 0; // no track added + + using mkvparser::Track; + + while (i != parser_tracks->GetTracksCount()) { + unsigned long track_num = i++; + if (switch_tracks) + track_num = i % parser_tracks->GetTracksCount(); + + const Track* const parser_track = parser_tracks->GetTrackByIndex(track_num); + + if (parser_track == NULL) + continue; + + // TODO(fgalligan): Add support for language to parser. + const char* const track_name = parser_track->GetNameAsUTF8(); + + const long long track_type = parser_track->GetType(); + + if (track_type == Track::kVideo && output_video) { + // Get the video track from the parser + const mkvparser::VideoTrack* const pVideoTrack = + static_cast<const mkvparser::VideoTrack*>(parser_track); + const long long width = pVideoTrack->GetWidth(); + const long long height = pVideoTrack->GetHeight(); + + // Add the video track to the muxer + vid_track = muxer_segment.AddVideoTrack(static_cast<int>(width), + static_cast<int>(height), + video_track_number); + if (!vid_track) { + printf("\n Could not add video track.\n"); + return EXIT_FAILURE; + } + + mkvmuxer::VideoTrack* const video = static_cast<mkvmuxer::VideoTrack*>( + muxer_segment.GetTrackByNumber(vid_track)); + if (!video) { + printf("\n Could not get video track.\n"); + return EXIT_FAILURE; + } + + if (pVideoTrack->GetColour()) { + mkvmuxer::Colour muxer_colour; + if (!libwebm::CopyColour(*pVideoTrack->GetColour(), &muxer_colour)) + return EXIT_FAILURE; + if (!video->SetColour(muxer_colour)) + return EXIT_FAILURE; + } + + if (pVideoTrack->GetProjection() || + projection_type != mkvparser::Projection::kTypeNotPresent) { + mkvmuxer::Projection muxer_projection; + const mkvparser::Projection* const parser_projection = + pVideoTrack->GetProjection(); + typedef mkvmuxer::Projection::ProjectionType MuxerProjType; + if (parser_projection && + !CopyVideoProjection(*parser_projection, &muxer_projection)) { + printf("\n Unable to copy video projection.\n"); + return EXIT_FAILURE; + } + // Override the values that came from parser if set on command line. + if (projection_type != mkvparser::Projection::kTypeNotPresent) { + muxer_projection.set_type( + static_cast<MuxerProjType>(projection_type)); + if (projection_type == mkvparser::Projection::kRectangular && + projection_file != NULL) { + printf("\n Rectangular projection must not have private data.\n"); + return EXIT_FAILURE; + } else if ((projection_type == mkvparser::Projection::kCubeMap || + projection_type == mkvparser::Projection::kMesh) && + projection_file == NULL) { + printf("\n Mesh or CubeMap projection must have private data.\n"); + return EXIT_FAILURE; + } + if (projection_file != NULL) { + std::string contents; + if (!libwebm::GetFileContents(projection_file, &contents) || + contents.size() == 0) { + printf("\n Failed to read file \"%s\" or file is empty\n", + projection_file); + return EXIT_FAILURE; + } + if (!muxer_projection.SetProjectionPrivate( + reinterpret_cast<uint8_t*>(&contents[0]), + contents.size())) { + printf("\n Failed to SetProjectionPrivate of length %zu.\n", + contents.size()); + return EXIT_FAILURE; + } + } + } + const float kValueNotPresent = mkvparser::Projection::kValueNotPresent; + if (projection_pose_yaw != kValueNotPresent) + muxer_projection.set_pose_yaw(projection_pose_yaw); + if (projection_pose_pitch != kValueNotPresent) + muxer_projection.set_pose_pitch(projection_pose_pitch); + if (projection_pose_roll != kValueNotPresent) + muxer_projection.set_pose_roll(projection_pose_roll); + + if (!video->SetProjection(muxer_projection)) + return EXIT_FAILURE; + } + + if (track_name) + video->set_name(track_name); + + video->set_codec_id(pVideoTrack->GetCodecId()); + + if (display_width > 0) + video->set_display_width(display_width); + if (display_height > 0) + video->set_display_height(display_height); + if (pixel_width > 0) + video->set_pixel_width(pixel_width); + if (pixel_height > 0) + video->set_pixel_height(pixel_height); + if (stereo_mode > 0) + video->SetStereoMode(stereo_mode); + + const double rate = pVideoTrack->GetFrameRate(); + if (rate > 0.0) { + video->set_frame_rate(rate); + } + + size_t parser_private_size; + const unsigned char* const parser_private_data = + pVideoTrack->GetCodecPrivate(parser_private_size); + + if (!strcmp(video->codec_id(), mkvmuxer::Tracks::kAv1CodecId)) { + if (parser_private_data == NULL || parser_private_size == 0) { + printf("AV1 input track has no CodecPrivate. %s is invalid.", input); + return EXIT_FAILURE; + } + } + + if (!strcmp(video->codec_id(), mkvmuxer::Tracks::kVp9CodecId) && + (vp9_profile >= 0 || vp9_level >= 0)) { + const int kMaxVp9PrivateSize = 6; + unsigned char vp9_private_data[kMaxVp9PrivateSize]; + int vp9_private_size = 0; + if (vp9_profile >= 0) { + if (vp9_profile < 0 || vp9_profile > 3) { + printf("\n VP9 profile(%d) is not valid.\n", vp9_profile); + return EXIT_FAILURE; + } + const uint8_t kVp9ProfileId = 1; + const uint8_t kVp9ProfileIdLength = 1; + vp9_private_data[vp9_private_size++] = kVp9ProfileId; + vp9_private_data[vp9_private_size++] = kVp9ProfileIdLength; + vp9_private_data[vp9_private_size++] = vp9_profile; + } + + if (vp9_level >= 0) { + const int kNumLevels = 14; + const int levels[kNumLevels] = {10, 11, 20, 21, 30, 31, 40, + 41, 50, 51, 52, 60, 61, 62}; + bool level_is_valid = false; + for (int i = 0; i < kNumLevels; ++i) { + if (vp9_level == levels[i]) { + level_is_valid = true; + break; + } + } + if (!level_is_valid) { + printf("\n VP9 level(%d) is not valid.\n", vp9_level); + return EXIT_FAILURE; + } + const uint8_t kVp9LevelId = 2; + const uint8_t kVp9LevelIdLength = 1; + vp9_private_data[vp9_private_size++] = kVp9LevelId; + vp9_private_data[vp9_private_size++] = kVp9LevelIdLength; + vp9_private_data[vp9_private_size++] = vp9_level; + } + if (!video->SetCodecPrivate(vp9_private_data, vp9_private_size)) { + printf("\n Could not add video private data.\n"); + return EXIT_FAILURE; + } + } else if (parser_private_data && parser_private_size > 0) { + if (!video->SetCodecPrivate(parser_private_data, parser_private_size)) { + printf("\n Could not add video private data.\n"); + return EXIT_FAILURE; + } + } + } else if (track_type == Track::kAudio && output_audio) { + // Get the audio track from the parser + const mkvparser::AudioTrack* const pAudioTrack = + static_cast<const mkvparser::AudioTrack*>(parser_track); + const long long channels = pAudioTrack->GetChannels(); + const double sample_rate = pAudioTrack->GetSamplingRate(); + + // Add the audio track to the muxer + aud_track = muxer_segment.AddAudioTrack(static_cast<int>(sample_rate), + static_cast<int>(channels), + audio_track_number); + if (!aud_track) { + printf("\n Could not add audio track.\n"); + return EXIT_FAILURE; + } + + mkvmuxer::AudioTrack* const audio = static_cast<mkvmuxer::AudioTrack*>( + muxer_segment.GetTrackByNumber(aud_track)); + if (!audio) { + printf("\n Could not get audio track.\n"); + return EXIT_FAILURE; + } + + if (track_name) + audio->set_name(track_name); + + audio->set_codec_id(pAudioTrack->GetCodecId()); + + size_t private_size; + const unsigned char* const private_data = + pAudioTrack->GetCodecPrivate(private_size); + if (private_size > 0) { + if (!audio->SetCodecPrivate(private_data, private_size)) { + printf("\n Could not add audio private data.\n"); + return EXIT_FAILURE; + } + } + + const long long bit_depth = pAudioTrack->GetBitDepth(); + if (bit_depth > 0) + audio->set_bit_depth(bit_depth); + + if (pAudioTrack->GetCodecDelay()) + audio->set_codec_delay(pAudioTrack->GetCodecDelay()); + if (pAudioTrack->GetSeekPreRoll()) + audio->set_seek_pre_roll(pAudioTrack->GetSeekPreRoll()); + } + } + + // We have created all the video and audio tracks. If any WebVTT + // files were specified as command-line args, then parse them and + // add a track to the output file corresponding to each metadata + // input file. + + SampleMuxerMetadata metadata; + + if (!metadata.Init(&muxer_segment)) { + printf("\n Could not initialize metadata cache.\n"); + return EXIT_FAILURE; + } + + if (!LoadMetadataFiles(metadata_files, &metadata)) + return EXIT_FAILURE; + + if (!metadata.AddChapters()) + return EXIT_FAILURE; + + // Set Cues element attributes + mkvmuxer::Cues* const cues = muxer_segment.GetCues(); + cues->set_output_block_number(output_cues_block_number); + if (cues_on_video_track && vid_track) + muxer_segment.CuesTrack(vid_track); + if (cues_on_audio_track && aud_track) + muxer_segment.CuesTrack(aud_track); + + // Write clusters + unsigned char* data = NULL; + long data_len = 0; + + const mkvparser::Cluster* cluster = parser_segment->GetFirst(); + + while (cluster != NULL && !cluster->EOS()) { + const mkvparser::BlockEntry* block_entry; + + long status = cluster->GetFirst(block_entry); + + if (status) { + printf("\n Could not get first block of cluster.\n"); + return EXIT_FAILURE; + } + + while (block_entry != NULL && !block_entry->EOS()) { + const mkvparser::Block* const block = block_entry->GetBlock(); + const long long trackNum = block->GetTrackNumber(); + const mkvparser::Track* const parser_track = + parser_tracks->GetTrackByNumber(static_cast<unsigned long>(trackNum)); + + // When |parser_track| is NULL, it means that the track number in the + // Block is invalid (i.e.) the was no TrackEntry corresponding to the + // track number. So we reject the file. + if (!parser_track) { + return EXIT_FAILURE; + } + + const long long track_type = parser_track->GetType(); + const long long time_ns = block->GetTime(cluster); + + // Flush any metadata frames to the output file, before we write + // the current block. + if (!metadata.Write(time_ns)) + return EXIT_FAILURE; + + if ((track_type == Track::kAudio && output_audio) || + (track_type == Track::kVideo && output_video)) { + const int frame_count = block->GetFrameCount(); + + for (int i = 0; i < frame_count; ++i) { + const mkvparser::Block::Frame& frame = block->GetFrame(i); + + if (frame.len > data_len) { + delete[] data; + data = new unsigned char[frame.len]; + if (!data) + return EXIT_FAILURE; + data_len = frame.len; + } + + if (frame.Read(&reader, data)) + return EXIT_FAILURE; + + mkvmuxer::Frame muxer_frame; + if (!muxer_frame.Init(data, frame.len)) + return EXIT_FAILURE; + muxer_frame.set_track_number(track_type == Track::kAudio ? aud_track + : vid_track); + if (block->GetDiscardPadding()) + muxer_frame.set_discard_padding(block->GetDiscardPadding()); + muxer_frame.set_timestamp(time_ns); + muxer_frame.set_is_key(block->IsKey()); + if (!muxer_segment.AddGenericFrame(&muxer_frame)) { + printf("\n Could not add frame.\n"); + return EXIT_FAILURE; + } + } + } + + status = cluster->GetNext(block_entry, block_entry); + + if (status) { + printf("\n Could not get next block of cluster.\n"); + return EXIT_FAILURE; + } + } + + cluster = parser_segment->GetNext(cluster); + } + + // We have exhausted all video and audio frames in the input file. + // Flush any remaining metadata frames to the output file. + if (!metadata.Write(-1)) + return EXIT_FAILURE; + + if (copy_input_duration) { + const double input_duration = + static_cast<double>(segment_info->GetDuration()) / timeCodeScale; + muxer_segment.set_duration(input_duration); + } + + if (!muxer_segment.Finalize()) { + printf("Finalization of segment failed.\n"); + return EXIT_FAILURE; + } + + reader.Close(); + writer.Close(); + + if (cues_before_clusters) { + if (reader.Open(temp_file.c_str())) { + printf("\n Filename is invalid or error while opening.\n"); + return EXIT_FAILURE; + } + if (!writer.Open(output)) { + printf("\n Filename is invalid or error while opening.\n"); + return EXIT_FAILURE; + } + if (!muxer_segment.CopyAndMoveCuesBeforeClusters(&reader, &writer)) { + printf("\n Unable to copy and move cues before clusters.\n"); + return EXIT_FAILURE; + } + reader.Close(); + writer.Close(); + remove(temp_file.c_str()); + } + + delete[] data; + + return EXIT_SUCCESS; +} diff --git a/mkvmuxertypes.hpp b/mkvmuxertypes.hpp new file mode 100644 index 0000000..78478f4 --- /dev/null +++ b/mkvmuxertypes.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_MKVMUXERTYPES_HPP_ +#define LIBWEBM_MKVMUXERTYPES_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "mkvmuxer/mkvmuxertypes.h" + +#endif // LIBWEBM_MKVMUXERTYPES_HPP_ diff --git a/mkvmuxerutil.hpp b/mkvmuxerutil.hpp new file mode 100644 index 0000000..a26ba18 --- /dev/null +++ b/mkvmuxerutil.hpp @@ -0,0 +1,18 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_MKVMUXERUTIL_HPP_ +#define LIBWEBM_MKVMUXERUTIL_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "mkvmuxer/mkvmuxerutil.h" + +using mkvmuxer::EbmlElementSize; +using mkvmuxer::EbmlMasterElementSize; + +#endif // LIBWEBM_MKVMUXERUTIL_HPP_ diff --git a/mkvparser.hpp b/mkvparser.hpp new file mode 100644 index 0000000..3f86292 --- /dev/null +++ b/mkvparser.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_MKVPARSER_HPP_ +#define LIBWEBM_MKVPARSER_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "mkvparser/mkvparser.h" + +#endif // LIBWEBM_MKVPARSER_HPP_ diff --git a/mkvparser/mkvparser.cc b/mkvparser/mkvparser.cc new file mode 100644 index 0000000..412e6a5 --- /dev/null +++ b/mkvparser/mkvparser.cc @@ -0,0 +1,8076 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "mkvparser/mkvparser.h" + +#if defined(_MSC_VER) && _MSC_VER < 1800 +#include <float.h> // _isnan() / _finite() +#define MSC_COMPAT +#endif + +#include <cassert> +#include <cfloat> +#include <climits> +#include <cmath> +#include <cstring> +#include <memory> +#include <new> + +#include "common/webmids.h" + +namespace mkvparser { +const long long kStringElementSizeLimit = 20 * 1000 * 1000; +const float MasteringMetadata::kValueNotPresent = FLT_MAX; +const long long Colour::kValueNotPresent = LLONG_MAX; +const float Projection::kValueNotPresent = FLT_MAX; + +#ifdef MSC_COMPAT +inline bool isnan(double val) { return !!_isnan(val); } +inline bool isinf(double val) { return !_finite(val); } +#else +inline bool isnan(double val) { return std::isnan(val); } +inline bool isinf(double val) { return std::isinf(val); } +#endif // MSC_COMPAT + +template <typename Type> +Type* SafeArrayAlloc(unsigned long long num_elements, + unsigned long long element_size) { + if (num_elements == 0 || element_size == 0) + return NULL; + + const size_t kMaxAllocSize = 0x80000000; // 2GiB + const unsigned long long num_bytes = num_elements * element_size; + if (element_size > (kMaxAllocSize / num_elements)) + return NULL; + if (num_bytes != static_cast<size_t>(num_bytes)) + return NULL; + + return new (std::nothrow) Type[static_cast<size_t>(num_bytes)]; +} + +void GetVersion(int& major, int& minor, int& build, int& revision) { + major = 1; + minor = 0; + build = 0; + revision = 30; +} + +long long ReadUInt(IMkvReader* pReader, long long pos, long& len) { + if (!pReader || pos < 0) + return E_FILE_FORMAT_INVALID; + + len = 1; + unsigned char b; + int status = pReader->Read(pos, 1, &b); + + if (status < 0) // error or underflow + return status; + + if (status > 0) // interpreted as "underflow" + return E_BUFFER_NOT_FULL; + + if (b == 0) // we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) { + m >>= 1; + ++len; + } + + long long result = b & (~m); + ++pos; + + for (int i = 1; i < len; ++i) { + status = pReader->Read(pos, 1, &b); + + if (status < 0) { + len = 1; + return status; + } + + if (status > 0) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + +// Reads an EBML ID and returns it. +// An ID must at least 1 byte long, cannot exceed 4, and its value must be +// greater than 0. +// See known EBML values and EBMLMaxIDLength: +// http://www.matroska.org/technical/specs/index.html +// Returns the ID, or a value less than 0 to report an error while reading the +// ID. +long long ReadID(IMkvReader* pReader, long long pos, long& len) { + if (pReader == NULL || pos < 0) + return E_FILE_FORMAT_INVALID; + + // Read the first byte. The length in bytes of the ID is determined by + // finding the first set bit in the first byte of the ID. + unsigned char temp_byte = 0; + int read_status = pReader->Read(pos, 1, &temp_byte); + + if (read_status < 0) + return E_FILE_FORMAT_INVALID; + else if (read_status > 0) // No data to read. + return E_BUFFER_NOT_FULL; + + if (temp_byte == 0) // ID length > 8 bytes; invalid file. + return E_FILE_FORMAT_INVALID; + + int bit_pos = 0; + const int kMaxIdLengthInBytes = 4; + const int kCheckByte = 0x80; + + // Find the first bit that's set. + bool found_bit = false; + for (; bit_pos < kMaxIdLengthInBytes; ++bit_pos) { + if ((kCheckByte >> bit_pos) & temp_byte) { + found_bit = true; + break; + } + } + + if (!found_bit) { + // The value is too large to be a valid ID. + return E_FILE_FORMAT_INVALID; + } + + // Read the remaining bytes of the ID (if any). + const int id_length = bit_pos + 1; + long long ebml_id = temp_byte; + for (int i = 1; i < id_length; ++i) { + ebml_id <<= 8; + read_status = pReader->Read(pos + i, 1, &temp_byte); + + if (read_status < 0) + return E_FILE_FORMAT_INVALID; + else if (read_status > 0) + return E_BUFFER_NOT_FULL; + + ebml_id |= temp_byte; + } + + len = id_length; + return ebml_id; +} + +long long GetUIntLength(IMkvReader* pReader, long long pos, long& len) { + if (!pReader || pos < 0) + return E_FILE_FORMAT_INVALID; + + long long total, available; + + int status = pReader->Length(&total, &available); + if (status < 0 || (total >= 0 && available > total)) + return E_FILE_FORMAT_INVALID; + + len = 1; + + if (pos >= available) + return pos; // too few bytes available + + unsigned char b; + + status = pReader->Read(pos, 1, &b); + + if (status != 0) + return status; + + if (b == 0) // we can't handle u-int values larger than 8 bytes + return E_FILE_FORMAT_INVALID; + + unsigned char m = 0x80; + + while (!(b & m)) { + m >>= 1; + ++len; + } + + return 0; // success +} + +// TODO(vigneshv): This function assumes that unsigned values never have their +// high bit set. +long long UnserializeUInt(IMkvReader* pReader, long long pos, long long size) { + if (!pReader || pos < 0 || (size <= 0) || (size > 8)) + return E_FILE_FORMAT_INVALID; + + long long result = 0; + + for (long long i = 0; i < size; ++i) { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + return result; +} + +long UnserializeFloat(IMkvReader* pReader, long long pos, long long size_, + double& result) { + if (!pReader || pos < 0 || ((size_ != 4) && (size_ != 8))) + return E_FILE_FORMAT_INVALID; + + const long size = static_cast<long>(size_); + + unsigned char buf[8]; + + const int status = pReader->Read(pos, size, buf); + + if (status < 0) // error + return status; + + if (size == 4) { + union { + float f; + unsigned long ff; + }; + + ff = 0; + + for (int i = 0;;) { + ff |= buf[i]; + + if (++i >= 4) + break; + + ff <<= 8; + } + + result = f; + } else { + union { + double d; + unsigned long long dd; + }; + + dd = 0; + + for (int i = 0;;) { + dd |= buf[i]; + + if (++i >= 8) + break; + + dd <<= 8; + } + + result = d; + } + + if (mkvparser::isinf(result) || mkvparser::isnan(result)) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +long UnserializeInt(IMkvReader* pReader, long long pos, long long size, + long long& result_ref) { + if (!pReader || pos < 0 || size < 1 || size > 8) + return E_FILE_FORMAT_INVALID; + + signed char first_byte = 0; + const long status = pReader->Read(pos, 1, (unsigned char*)&first_byte); + + if (status < 0) + return status; + + unsigned long long result = first_byte; + ++pos; + + for (long i = 1; i < size; ++i) { + unsigned char b; + + const long status = pReader->Read(pos, 1, &b); + + if (status < 0) + return status; + + result <<= 8; + result |= b; + + ++pos; + } + + result_ref = static_cast<long long>(result); + return 0; +} + +long UnserializeString(IMkvReader* pReader, long long pos, long long size, + char*& str) { + delete[] str; + str = NULL; + + if (size >= LONG_MAX || size < 0 || size > kStringElementSizeLimit) + return E_FILE_FORMAT_INVALID; + + // +1 for '\0' terminator + const long required_size = static_cast<long>(size) + 1; + + str = SafeArrayAlloc<char>(1, required_size); + if (str == NULL) + return E_FILE_FORMAT_INVALID; + + unsigned char* const buf = reinterpret_cast<unsigned char*>(str); + + const long status = pReader->Read(pos, static_cast<long>(size), buf); + + if (status) { + delete[] str; + str = NULL; + + return status; + } + + str[required_size - 1] = '\0'; + return 0; +} + +long ParseElementHeader(IMkvReader* pReader, long long& pos, long long stop, + long long& id, long long& size) { + if (stop >= 0 && pos >= stop) + return E_FILE_FORMAT_INVALID; + + long len; + + id = ReadID(pReader, pos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume id + + if (stop >= 0 && pos >= stop) + return E_FILE_FORMAT_INVALID; + + size = ReadUInt(pReader, pos, len); + + if (size < 0 || len < 1 || len > 8) { + // Invalid: Negative payload size, negative or 0 length integer, or integer + // larger than 64 bits (libwebm cannot handle them). + return E_FILE_FORMAT_INVALID; + } + + // Avoid rolling over pos when very close to LLONG_MAX. + const unsigned long long rollover_check = + static_cast<unsigned long long>(pos) + len; + if (rollover_check > LLONG_MAX) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume length of size + + // pos now designates payload + + if (stop >= 0 && pos > stop) + return E_FILE_FORMAT_INVALID; + + return 0; // success +} + +bool Match(IMkvReader* pReader, long long& pos, unsigned long expected_id, + long long& val) { + if (!pReader || pos < 0) + return false; + + long long total = 0; + long long available = 0; + + const long status = pReader->Length(&total, &available); + if (status < 0 || (total >= 0 && available > total)) + return false; + + long len = 0; + + const long long id = ReadID(pReader, pos, len); + if (id < 0 || (available - pos) > len) + return false; + + if (static_cast<unsigned long>(id) != expected_id) + return false; + + pos += len; // consume id + + const long long size = ReadUInt(pReader, pos, len); + if (size < 0 || size > 8 || len < 1 || len > 8 || (available - pos) > len) + return false; + + pos += len; // consume length of size of payload + + val = UnserializeUInt(pReader, pos, size); + if (val < 0) + return false; + + pos += size; // consume size of payload + + return true; +} + +bool Match(IMkvReader* pReader, long long& pos, unsigned long expected_id, + unsigned char*& buf, size_t& buflen) { + if (!pReader || pos < 0) + return false; + + long long total = 0; + long long available = 0; + + long status = pReader->Length(&total, &available); + if (status < 0 || (total >= 0 && available > total)) + return false; + + long len = 0; + const long long id = ReadID(pReader, pos, len); + if (id < 0 || (available - pos) > len) + return false; + + if (static_cast<unsigned long>(id) != expected_id) + return false; + + pos += len; // consume id + + const long long size = ReadUInt(pReader, pos, len); + if (size < 0 || len <= 0 || len > 8 || (available - pos) > len) + return false; + + unsigned long long rollover_check = + static_cast<unsigned long long>(pos) + len; + if (rollover_check > LLONG_MAX) + return false; + + pos += len; // consume length of size of payload + + rollover_check = static_cast<unsigned long long>(pos) + size; + if (rollover_check > LLONG_MAX) + return false; + + if ((pos + size) > available) + return false; + + if (size >= LONG_MAX) + return false; + + const long buflen_ = static_cast<long>(size); + + buf = SafeArrayAlloc<unsigned char>(1, buflen_); + if (!buf) + return false; + + status = pReader->Read(pos, buflen_, buf); + if (status != 0) + return false; + + buflen = buflen_; + + pos += size; // consume size of payload + return true; +} + +EBMLHeader::EBMLHeader() : m_docType(NULL) { Init(); } + +EBMLHeader::~EBMLHeader() { delete[] m_docType; } + +void EBMLHeader::Init() { + m_version = 1; + m_readVersion = 1; + m_maxIdLength = 4; + m_maxSizeLength = 8; + + if (m_docType) { + delete[] m_docType; + m_docType = NULL; + } + + m_docTypeVersion = 1; + m_docTypeReadVersion = 1; +} + +long long EBMLHeader::Parse(IMkvReader* pReader, long long& pos) { + if (!pReader) + return E_FILE_FORMAT_INVALID; + + long long total, available; + + long status = pReader->Length(&total, &available); + + if (status < 0) // error + return status; + + pos = 0; + + // Scan until we find what looks like the first byte of the EBML header. + const long long kMaxScanBytes = (available >= 1024) ? 1024 : available; + const unsigned char kEbmlByte0 = 0x1A; + unsigned char scan_byte = 0; + + while (pos < kMaxScanBytes) { + status = pReader->Read(pos, 1, &scan_byte); + + if (status < 0) // error + return status; + else if (status > 0) + return E_BUFFER_NOT_FULL; + + if (scan_byte == kEbmlByte0) + break; + + ++pos; + } + + long len = 0; + const long long ebml_id = ReadID(pReader, pos, len); + + if (ebml_id == E_BUFFER_NOT_FULL) + return E_BUFFER_NOT_FULL; + + if (len != 4 || ebml_id != libwebm::kMkvEBML) + return E_FILE_FORMAT_INVALID; + + // Move read pos forward to the EBML header size field. + pos += 4; + + // Read length of size field. + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return E_FILE_FORMAT_INVALID; + else if (result > 0) // need more data + return E_BUFFER_NOT_FULL; + + if (len < 1 || len > 8) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((total - pos) < len)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < len) + return pos + len; // try again later + + // Read the EBML header size. + result = ReadUInt(pReader, pos, len); + + if (result < 0) // error + return result; + + pos += len; // consume size field + + // pos now designates start of payload + + if ((total >= 0) && ((total - pos) < result)) + return E_FILE_FORMAT_INVALID; + + if ((available - pos) < result) + return pos + result; + + const long long end = pos + result; + + Init(); + + while (pos < end) { + long long id, size; + + status = ParseElementHeader(pReader, pos, end, id, size); + + if (status < 0) // error + return status; + + if (size == 0) + return E_FILE_FORMAT_INVALID; + + if (id == libwebm::kMkvEBMLVersion) { + m_version = UnserializeUInt(pReader, pos, size); + + if (m_version <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvEBMLReadVersion) { + m_readVersion = UnserializeUInt(pReader, pos, size); + + if (m_readVersion <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvEBMLMaxIDLength) { + m_maxIdLength = UnserializeUInt(pReader, pos, size); + + if (m_maxIdLength <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvEBMLMaxSizeLength) { + m_maxSizeLength = UnserializeUInt(pReader, pos, size); + + if (m_maxSizeLength <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvDocType) { + if (m_docType) + return E_FILE_FORMAT_INVALID; + + status = UnserializeString(pReader, pos, size, m_docType); + + if (status) // error + return status; + } else if (id == libwebm::kMkvDocTypeVersion) { + m_docTypeVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeVersion <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvDocTypeReadVersion) { + m_docTypeReadVersion = UnserializeUInt(pReader, pos, size); + + if (m_docTypeReadVersion <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; + } + + if (pos != end) + return E_FILE_FORMAT_INVALID; + + // Make sure DocType, DocTypeReadVersion, and DocTypeVersion are valid. + if (m_docType == NULL || m_docTypeReadVersion <= 0 || m_docTypeVersion <= 0) + return E_FILE_FORMAT_INVALID; + + // Make sure EBMLMaxIDLength and EBMLMaxSizeLength are valid. + if (m_maxIdLength <= 0 || m_maxIdLength > 4 || m_maxSizeLength <= 0 || + m_maxSizeLength > 8) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +Segment::Segment(IMkvReader* pReader, long long elem_start, + // long long elem_size, + long long start, long long size) + : m_pReader(pReader), + m_element_start(elem_start), + // m_element_size(elem_size), + m_start(start), + m_size(size), + m_pos(start), + m_pUnknownSize(0), + m_pSeekHead(NULL), + m_pInfo(NULL), + m_pTracks(NULL), + m_pCues(NULL), + m_pChapters(NULL), + m_pTags(NULL), + m_clusters(NULL), + m_clusterCount(0), + m_clusterPreloadCount(0), + m_clusterSize(0) {} + +Segment::~Segment() { + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** i = m_clusters; + Cluster** j = m_clusters + count; + + while (i != j) { + Cluster* const p = *i++; + delete p; + } + + delete[] m_clusters; + + delete m_pTracks; + delete m_pInfo; + delete m_pCues; + delete m_pChapters; + delete m_pTags; + delete m_pSeekHead; +} + +long long Segment::CreateInstance(IMkvReader* pReader, long long pos, + Segment*& pSegment) { + if (pReader == NULL || pos < 0) + return E_PARSE_FAILED; + + pSegment = NULL; + + long long total, available; + + const long status = pReader->Length(&total, &available); + + if (status < 0) // error + return status; + + if (available < 0) + return -1; + + if ((total >= 0) && (available > total)) + return -1; + + // I would assume that in practice this loop would execute + // exactly once, but we allow for other elements (e.g. Void) + // to immediately follow the EBML header. This is fine for + // the source filter case (since the entire file is available), + // but in the splitter case over a network we should probably + // just give up early. We could for example decide only to + // execute this loop a maximum of, say, 10 times. + // TODO: + // There is an implied "give up early" by only parsing up + // to the available limit. We do do that, but only if the + // total file size is unknown. We could decide to always + // use what's available as our limit (irrespective of whether + // we happen to know the total file length). This would have + // as its sense "parse this much of the file before giving up", + // which a slightly different sense from "try to parse up to + // 10 EMBL elements before giving up". + + for (;;) { + if ((total >= 0) && (pos >= total)) + return E_FILE_FORMAT_INVALID; + + // Read ID + long len; + long long result = GetUIntLength(pReader, pos, len); + + if (result) // error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadID(pReader, pos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume ID + + // Read Size + + result = GetUIntLength(pReader, pos, len); + + if (result) // error, or too few available bytes + return result; + + if ((total >= 0) && ((pos + len) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return size; + + pos += len; // consume length of size of element + + // Pos now points to start of payload + + // Handle "unknown size" for live streaming of webm files. + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (id == libwebm::kMkvSegment) { + if (size == unknown_size) + size = -1; + + else if (total < 0) + size = -1; + + else if ((pos + size) > total) + size = -1; + + pSegment = new (std::nothrow) Segment(pReader, idpos, pos, size); + if (pSegment == NULL) + return E_PARSE_FAILED; + + return 0; // success + } + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + size) > total)) + return E_FILE_FORMAT_INVALID; + + if ((pos + size) > available) + return pos + size; + + pos += size; // consume payload + } +} + +long long Segment::ParseHeaders() { + // Outermost (level 0) segment object has been constructed, + // and pos designates start of payload. We need to find the + // inner (level 1) elements. + long long total, available; + + const int status = m_pReader->Length(&total, &available); + + if (status < 0) // error + return status; + + if (total > 0 && available > total) + return E_FILE_FORMAT_INVALID; + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + if ((segment_stop >= 0 && total >= 0 && segment_stop > total) || + (segment_stop >= 0 && m_pos > segment_stop)) { + return E_FILE_FORMAT_INVALID; + } + + for (;;) { + if ((total >= 0) && (m_pos >= total)) + break; + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + break; + + long long pos = m_pos; + const long long element_start = pos; + + // Avoid rolling over pos when very close to LLONG_MAX. + unsigned long long rollover_check = pos + 1ULL; + if (rollover_check > LLONG_MAX) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > available) + return (pos + 1); + + long len; + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return result; + + if (result > 0) { + // MkvReader doesn't have enough data to satisfy this read attempt. + return (pos + 1); + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long idpos = pos; + const long long id = ReadID(m_pReader, idpos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + if (id == libwebm::kMkvCluster) + break; + + pos += len; // consume ID + + if ((pos + 1) > available) + return (pos + 1); + + // Read Size + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return result; + + if (result > 0) { + // MkvReader doesn't have enough data to satisfy this read attempt. + return (pos + 1); + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > available) + return pos + len; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0 || len < 1 || len > 8) { + // TODO(tomfinegan): ReadUInt should return an error when len is < 1 or + // len > 8 is true instead of checking this _everywhere_. + return size; + } + + pos += len; // consume length of size of element + + // Avoid rolling over pos when very close to LLONG_MAX. + rollover_check = static_cast<unsigned long long>(pos) + size; + if (rollover_check > LLONG_MAX) + return E_FILE_FORMAT_INVALID; + + const long long element_size = size + pos - element_start; + + // Pos now points to start of payload + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + // We read EBML elements either in total or nothing at all. + + if ((pos + size) > available) + return pos + size; + + if (id == libwebm::kMkvInfo) { + if (m_pInfo) + return E_FILE_FORMAT_INVALID; + + m_pInfo = new (std::nothrow) + SegmentInfo(this, pos, size, element_start, element_size); + + if (m_pInfo == NULL) + return -1; + + const long status = m_pInfo->Parse(); + + if (status) + return status; + } else if (id == libwebm::kMkvTracks) { + if (m_pTracks) + return E_FILE_FORMAT_INVALID; + + m_pTracks = new (std::nothrow) + Tracks(this, pos, size, element_start, element_size); + + if (m_pTracks == NULL) + return -1; + + const long status = m_pTracks->Parse(); + + if (status) + return status; + } else if (id == libwebm::kMkvCues) { + if (m_pCues == NULL) { + m_pCues = new (std::nothrow) + Cues(this, pos, size, element_start, element_size); + + if (m_pCues == NULL) + return -1; + } + } else if (id == libwebm::kMkvSeekHead) { + if (m_pSeekHead == NULL) { + m_pSeekHead = new (std::nothrow) + SeekHead(this, pos, size, element_start, element_size); + + if (m_pSeekHead == NULL) + return -1; + + const long status = m_pSeekHead->Parse(); + + if (status) + return status; + } + } else if (id == libwebm::kMkvChapters) { + if (m_pChapters == NULL) { + m_pChapters = new (std::nothrow) + Chapters(this, pos, size, element_start, element_size); + + if (m_pChapters == NULL) + return -1; + + const long status = m_pChapters->Parse(); + + if (status) + return status; + } + } else if (id == libwebm::kMkvTags) { + if (m_pTags == NULL) { + m_pTags = new (std::nothrow) + Tags(this, pos, size, element_start, element_size); + + if (m_pTags == NULL) + return -1; + + const long status = m_pTags->Parse(); + + if (status) + return status; + } + } + + m_pos = pos + size; // consume payload + } + + if (segment_stop >= 0 && m_pos > segment_stop) + return E_FILE_FORMAT_INVALID; + + if (m_pInfo == NULL) // TODO: liberalize this behavior + return E_FILE_FORMAT_INVALID; + + if (m_pTracks == NULL) + return E_FILE_FORMAT_INVALID; + + return 0; // success +} + +long Segment::LoadCluster(long long& pos, long& len) { + for (;;) { + const long result = DoLoadCluster(pos, len); + + if (result <= 1) + return result; + } +} + +long Segment::DoLoadCluster(long long& pos, long& len) { + if (m_pos < 0) + return DoLoadClusterUnknownSize(pos, len); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + if (total >= 0 && avail > total) + return E_FILE_FORMAT_INVALID; + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + long long cluster_off = -1; // offset relative to start of segment + long long cluster_size = -1; // size of cluster payload + + for (;;) { + if ((total >= 0) && (m_pos >= total)) + return 1; // no more clusters + + if ((segment_stop >= 0) && (m_pos >= segment_stop)) + return 1; // no more clusters + + pos = m_pos; + + // Read ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadID(m_pReader, idpos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume ID + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + pos += len; // consume length of size of element + + // pos now points to start of payload + + if (size == 0) { + // Missing element payload: move on. + m_pos = pos; + continue; + } + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if ((segment_stop >= 0) && (size != unknown_size) && + ((pos + size) > segment_stop)) { + return E_FILE_FORMAT_INVALID; + } + + if (id == libwebm::kMkvCues) { + if (size == unknown_size) { + // Cues element of unknown size: Not supported. + return E_FILE_FORMAT_INVALID; + } + + if (m_pCues == NULL) { + const long long element_size = (pos - idpos) + size; + + m_pCues = new (std::nothrow) Cues(this, pos, size, idpos, element_size); + if (m_pCues == NULL) + return -1; + } + + m_pos = pos + size; // consume payload + continue; + } + + if (id != libwebm::kMkvCluster) { + // Besides the Segment, Libwebm allows only cluster elements of unknown + // size. Fail the parse upon encountering a non-cluster element reporting + // unknown size. + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + m_pos = pos + size; // consume payload + continue; + } + + // We have a cluster. + + cluster_off = idpos - m_start; // relative pos + + if (size != unknown_size) + cluster_size = size; + + break; + } + + if (cluster_off < 0) { + // No cluster, die. + return E_FILE_FORMAT_INVALID; + } + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, cluster_off, pos_, len_); + + if (status < 0) { // error, or underflow + pos = pos_; + len = len_; + + return status; + } + + // status == 0 means "no block entries found" + // status > 0 means "found at least one block entry" + + // TODO: + // The issue here is that the segment increments its own + // pos ptr past the most recent cluster parsed, and then + // starts from there to parse the next cluster. If we + // don't know the size of the current cluster, then we + // must either parse its payload (as we do below), looking + // for the cluster (or cues) ID to terminate the parse. + // This isn't really what we want: rather, we really need + // a way to create the curr cluster object immediately. + // The pity is that cluster::parse can determine its own + // boundary, and we largely duplicate that same logic here. + // + // Maybe we need to get rid of our look-ahead preloading + // in source::parse??? + // + // As we're parsing the blocks in the curr cluster + //(in cluster::parse), we should have some way to signal + // to the segment that we have determined the boundary, + // so it can adjust its own segment::m_pos member. + // + // The problem is that we're asserting in asyncreadinit, + // because we adjust the pos down to the curr seek pos, + // and the resulting adjusted len is > 2GB. I'm suspicious + // that this is even correct, but even if it is, we can't + // be loading that much data in the cache anyway. + + const long idx = m_clusterCount; + + if (m_clusterPreloadCount > 0) { + if (idx >= m_clusterSize) + return E_FILE_FORMAT_INVALID; + + Cluster* const pCluster = m_clusters[idx]; + if (pCluster == NULL || pCluster->m_index >= 0) + return E_FILE_FORMAT_INVALID; + + const long long off = pCluster->GetPosition(); + if (off < 0) + return E_FILE_FORMAT_INVALID; + + if (off == cluster_off) { // preloaded already + if (status == 0) // no entries found + return E_FILE_FORMAT_INVALID; + + if (cluster_size >= 0) + pos += cluster_size; + else { + const long long element_size = pCluster->GetElementSize(); + + if (element_size <= 0) + return E_FILE_FORMAT_INVALID; // TODO: handle this case + + pos = pCluster->m_element_start + element_size; + } + + pCluster->m_index = idx; // move from preloaded to loaded + ++m_clusterCount; + --m_clusterPreloadCount; + + m_pos = pos; // consume payload + if (segment_stop >= 0 && m_pos > segment_stop) + return E_FILE_FORMAT_INVALID; + + return 0; // success + } + } + + if (status == 0) { // no entries found + if (cluster_size >= 0) + pos += cluster_size; + + if ((total >= 0) && (pos >= total)) { + m_pos = total; + return 1; // no more clusters + } + + if ((segment_stop >= 0) && (pos >= segment_stop)) { + m_pos = segment_stop; + return 1; // no more clusters + } + + m_pos = pos; + return 2; // try again + } + + // status > 0 means we have an entry + + Cluster* const pCluster = Cluster::Create(this, idx, cluster_off); + if (pCluster == NULL) + return -1; + + if (!AppendCluster(pCluster)) { + delete pCluster; + return -1; + } + + if (cluster_size >= 0) { + pos += cluster_size; + + m_pos = pos; + + if (segment_stop > 0 && m_pos > segment_stop) + return E_FILE_FORMAT_INVALID; + + return 0; + } + + m_pUnknownSize = pCluster; + m_pos = -pos; + + return 0; // partial success, since we have a new cluster + + // status == 0 means "no block entries found" + // pos designates start of payload + // m_pos has NOT been adjusted yet (in case we need to come back here) +} + +long Segment::DoLoadClusterUnknownSize(long long& pos, long& len) { + if (m_pos >= 0 || m_pUnknownSize == NULL) + return E_PARSE_FAILED; + + const long status = m_pUnknownSize->Parse(pos, len); + + if (status < 0) // error or underflow + return status; + + if (status == 0) // parsed a block + return 2; // continue parsing + + const long long start = m_pUnknownSize->m_element_start; + const long long size = m_pUnknownSize->GetElementSize(); + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + pos = start + size; + m_pos = pos; + + m_pUnknownSize = 0; + + return 2; // continue parsing +} + +bool Segment::AppendCluster(Cluster* pCluster) { + if (pCluster == NULL || pCluster->m_index < 0) + return false; + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + const long idx = pCluster->m_index; + + if (size < count || idx != m_clusterCount) + return false; + + if (count >= size) { + const long n = (size <= 0) ? 2048 : 2 * size; + + Cluster** const qq = new (std::nothrow) Cluster*[n]; + if (qq == NULL) + return false; + + Cluster** q = qq; + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + if (m_clusterPreloadCount > 0) { + Cluster** const p = m_clusters + m_clusterCount; + if (*p == NULL || (*p)->m_index >= 0) + return false; + + Cluster** q = p + m_clusterPreloadCount; + if (q >= (m_clusters + size)) + return false; + + for (;;) { + Cluster** const qq = q - 1; + if ((*qq)->m_index >= 0) + return false; + + *q = *qq; + q = qq; + + if (q == p) + break; + } + } + + m_clusters[idx] = pCluster; + ++m_clusterCount; + return true; +} + +bool Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) { + if (pCluster == NULL || pCluster->m_index >= 0 || idx < m_clusterCount) + return false; + + const long count = m_clusterCount + m_clusterPreloadCount; + + long& size = m_clusterSize; + if (size < count) + return false; + + if (count >= size) { + const long n = (size <= 0) ? 2048 : 2 * size; + + Cluster** const qq = new (std::nothrow) Cluster*[n]; + if (qq == NULL) + return false; + Cluster** q = qq; + + Cluster** p = m_clusters; + Cluster** const pp = p + count; + + while (p != pp) + *q++ = *p++; + + delete[] m_clusters; + + m_clusters = qq; + size = n; + } + + if (m_clusters == NULL) + return false; + + Cluster** const p = m_clusters + idx; + + Cluster** q = m_clusters + count; + if (q < p || q >= (m_clusters + size)) + return false; + + while (q > p) { + Cluster** const qq = q - 1; + + if ((*qq)->m_index >= 0) + return false; + + *q = *qq; + q = qq; + } + + m_clusters[idx] = pCluster; + ++m_clusterPreloadCount; + return true; +} + +long Segment::Load() { + if (m_clusters != NULL || m_clusterSize != 0 || m_clusterCount != 0) + return E_PARSE_FAILED; + + // Outermost (level 0) segment object has been constructed, + // and pos designates start of payload. We need to find the + // inner (level 1) elements. + + const long long header_status = ParseHeaders(); + + if (header_status < 0) // error + return static_cast<long>(header_status); + + if (header_status > 0) // underflow + return E_BUFFER_NOT_FULL; + + if (m_pInfo == NULL || m_pTracks == NULL) + return E_FILE_FORMAT_INVALID; + + for (;;) { + const long status = LoadCluster(); + + if (status < 0) // error + return status; + + if (status >= 1) // no more clusters + return 0; + } +} + +SeekHead::Entry::Entry() : id(0), pos(0), element_start(0), element_size(0) {} + +SeekHead::SeekHead(Segment* pSegment, long long start, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_entries(0), + m_entry_count(0), + m_void_elements(0), + m_void_element_count(0) {} + +SeekHead::~SeekHead() { + delete[] m_entries; + delete[] m_void_elements; +} + +long SeekHead::Parse() { + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + // first count the seek head entries + + int entry_count = 0; + int void_element_count = 0; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == libwebm::kMkvSeek) + ++entry_count; + else if (id == libwebm::kMkvVoid) + ++void_element_count; + + pos += size; // consume payload + + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + if (entry_count > 0) { + m_entries = new (std::nothrow) Entry[entry_count]; + + if (m_entries == NULL) + return -1; + } + + if (void_element_count > 0) { + m_void_elements = new (std::nothrow) VoidElement[void_element_count]; + + if (m_void_elements == NULL) + return -1; + } + + // now parse the entries and void elements + + Entry* pEntry = m_entries; + VoidElement* pVoidElement = m_void_elements; + + pos = m_start; + + while (pos < stop) { + const long long idpos = pos; + + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == libwebm::kMkvSeek && entry_count > 0) { + if (ParseEntry(pReader, pos, size, pEntry)) { + Entry& e = *pEntry++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + } else if (id == libwebm::kMkvVoid && void_element_count > 0) { + VoidElement& e = *pVoidElement++; + + e.element_start = idpos; + e.element_size = (pos + size) - idpos; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries); + assert(count_ >= 0); + assert(count_ <= entry_count); + + m_entry_count = static_cast<int>(count_); + + count_ = ptrdiff_t(pVoidElement - m_void_elements); + assert(count_ >= 0); + assert(count_ <= void_element_count); + + m_void_element_count = static_cast<int>(count_); + + return 0; +} + +int SeekHead::GetCount() const { return m_entry_count; } + +const SeekHead::Entry* SeekHead::GetEntry(int idx) const { + if (idx < 0) + return 0; + + if (idx >= m_entry_count) + return 0; + + return m_entries + idx; +} + +int SeekHead::GetVoidElementCount() const { return m_void_element_count; } + +const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const { + if (idx < 0) + return 0; + + if (idx >= m_void_element_count) + return 0; + + return m_void_elements + idx; +} + +long Segment::ParseCues(long long off, long long& pos, long& len) { + if (m_pCues) + return 0; // success + + if (off < 0) + return -1; + + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + pos = m_start + off; + + if ((total < 0) || (pos >= total)) + return 1; // don't bother parsing cues + + const long long element_start = pos; + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // underflow (weird) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + + const long long id = ReadID(m_pReader, idpos, len); + + if (id != libwebm::kMkvCues) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume ID + assert((segment_stop < 0) || (pos <= segment_stop)); + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // underflow (weird) + { + len = 1; + return E_BUFFER_NOT_FULL; + } + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + if (size == 0) // weird, although technically not illegal + return 1; // done + + pos += len; // consume length of size of element + assert((segment_stop < 0) || (pos <= segment_stop)); + + // Pos now points to start of payload + + const long long element_stop = pos + size; + + if ((segment_stop >= 0) && (element_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && (element_stop > total)) + return 1; // don't bother parsing anymore + + len = static_cast<long>(size); + + if (element_stop > avail) + return E_BUFFER_NOT_FULL; + + const long long element_size = element_stop - element_start; + + m_pCues = + new (std::nothrow) Cues(this, pos, size, element_start, element_size); + if (m_pCues == NULL) + return -1; + + return 0; // success +} + +bool SeekHead::ParseEntry(IMkvReader* pReader, long long start, long long size_, + Entry* pEntry) { + if (size_ <= 0) + return false; + + long long pos = start; + const long long stop = start + size_; + + long len; + + // parse the container for the level-1 element ID + + const long long seekIdId = ReadID(pReader, pos, len); + if (seekIdId < 0) + return false; + + if (seekIdId != libwebm::kMkvSeekID) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume SeekID id + + const long long seekIdSize = ReadUInt(pReader, pos, len); + + if (seekIdSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume size of field + + if ((pos + seekIdSize) > stop) + return false; + + pEntry->id = ReadID(pReader, pos, len); // payload + + if (pEntry->id <= 0) + return false; + + if (len != seekIdSize) + return false; + + pos += seekIdSize; // consume SeekID payload + + const long long seekPosId = ReadID(pReader, pos, len); + + if (seekPosId != libwebm::kMkvSeekPosition) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume id + + const long long seekPosSize = ReadUInt(pReader, pos, len); + + if (seekPosSize <= 0) + return false; + + if ((pos + len) > stop) + return false; + + pos += len; // consume size + + if ((pos + seekPosSize) > stop) + return false; + + pEntry->pos = UnserializeUInt(pReader, pos, seekPosSize); + + if (pEntry->pos < 0) + return false; + + pos += seekPosSize; // consume payload + + if (pos != stop) + return false; + + return true; +} + +Cues::Cues(Segment* pSegment, long long start_, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start_), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_cue_points(NULL), + m_count(0), + m_preload_count(0), + m_pos(start_) {} + +Cues::~Cues() { + const long n = m_count + m_preload_count; + + CuePoint** p = m_cue_points; + CuePoint** const q = p + n; + + while (p != q) { + CuePoint* const pCP = *p++; + assert(pCP); + + delete pCP; + } + + delete[] m_cue_points; +} + +long Cues::GetCount() const { + if (m_cue_points == NULL) + return -1; + + return m_count; // TODO: really ignore preload count? +} + +bool Cues::DoneParsing() const { + const long long stop = m_start + m_size; + return (m_pos >= stop); +} + +bool Cues::Init() const { + if (m_cue_points) + return true; + + if (m_count != 0 || m_preload_count != 0) + return false; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + const long long stop = m_start + m_size; + long long pos = m_start; + + long cue_points_size = 0; + + while (pos < stop) { + const long long idpos = pos; + + long len; + + const long long id = ReadID(pReader, pos, len); + if (id < 0 || (pos + len) > stop) { + return false; + } + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + if (size < 0 || (pos + len > stop)) { + return false; + } + + pos += len; // consume Size field + if (pos + size > stop) { + return false; + } + + if (id == libwebm::kMkvCuePoint) { + if (!PreloadCuePoint(cue_points_size, idpos)) + return false; + } + + pos += size; // skip payload + } + return true; +} + +bool Cues::PreloadCuePoint(long& cue_points_size, long long pos) const { + if (m_count != 0) + return false; + + if (m_preload_count >= cue_points_size) { + const long n = (cue_points_size <= 0) ? 2048 : 2 * cue_points_size; + + CuePoint** const qq = new (std::nothrow) CuePoint*[n]; + if (qq == NULL) + return false; + + CuePoint** q = qq; // beginning of target + + CuePoint** p = m_cue_points; // beginning of source + CuePoint** const pp = p + m_preload_count; // end of source + + while (p != pp) + *q++ = *p++; + + delete[] m_cue_points; + + m_cue_points = qq; + cue_points_size = n; + } + + CuePoint* const pCP = new (std::nothrow) CuePoint(m_preload_count, pos); + if (pCP == NULL) + return false; + + m_cue_points[m_preload_count++] = pCP; + return true; +} + +bool Cues::LoadCuePoint() const { + const long long stop = m_start + m_size; + + if (m_pos >= stop) + return false; // nothing else to do + + if (!Init()) { + m_pos = stop; + return false; + } + + IMkvReader* const pReader = m_pSegment->m_pReader; + + while (m_pos < stop) { + const long long idpos = m_pos; + + long len; + + const long long id = ReadID(pReader, m_pos, len); + if (id < 0 || (m_pos + len) > stop) + return false; + + m_pos += len; // consume ID + + const long long size = ReadUInt(pReader, m_pos, len); + if (size < 0 || (m_pos + len) > stop) + return false; + + m_pos += len; // consume Size field + if ((m_pos + size) > stop) + return false; + + if (id != libwebm::kMkvCuePoint) { + m_pos += size; // consume payload + if (m_pos > stop) + return false; + + continue; + } + + if (m_preload_count < 1) + return false; + + CuePoint* const pCP = m_cue_points[m_count]; + if (!pCP || (pCP->GetTimeCode() < 0 && (-pCP->GetTimeCode() != idpos))) + return false; + + if (!pCP->Load(pReader)) { + m_pos = stop; + return false; + } + ++m_count; + --m_preload_count; + + m_pos += size; // consume payload + if (m_pos > stop) + return false; + + return true; // yes, we loaded a cue point + } + + return false; // no, we did not load a cue point +} + +bool Cues::Find(long long time_ns, const Track* pTrack, const CuePoint*& pCP, + const CuePoint::TrackPosition*& pTP) const { + if (time_ns < 0 || pTrack == NULL || m_cue_points == NULL || m_count == 0) + return false; + + CuePoint** const ii = m_cue_points; + CuePoint** i = ii; + + CuePoint** const jj = ii + m_count; + CuePoint** j = jj; + + pCP = *i; + if (pCP == NULL) + return false; + + if (time_ns <= pCP->GetTime(m_pSegment)) { + pTP = pCP->Find(pTrack); + return (pTP != NULL); + } + + while (i < j) { + // INVARIANT: + //[ii, i) <= time_ns + //[i, j) ? + //[j, jj) > time_ns + + CuePoint** const k = i + (j - i) / 2; + if (k >= jj) + return false; + + CuePoint* const pCP = *k; + if (pCP == NULL) + return false; + + const long long t = pCP->GetTime(m_pSegment); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + if (i > j) + return false; + } + + if (i != j || i > jj || i <= ii) + return false; + + pCP = *--i; + + if (pCP == NULL || pCP->GetTime(m_pSegment) > time_ns) + return false; + + // TODO: here and elsewhere, it's probably not correct to search + // for the cue point with this time, and then search for a matching + // track. In principle, the matching track could be on some earlier + // cue point, and with our current algorithm, we'd miss it. To make + // this bullet-proof, we'd need to create a secondary structure, + // with a list of cue points that apply to a track, and then search + // that track-based structure for a matching cue point. + + pTP = pCP->Find(pTrack); + return (pTP != NULL); +} + +const CuePoint* Cues::GetFirst() const { + if (m_cue_points == NULL || m_count == 0) + return NULL; + + CuePoint* const* const pp = m_cue_points; + if (pp == NULL) + return NULL; + + CuePoint* const pCP = pp[0]; + if (pCP == NULL || pCP->GetTimeCode() < 0) + return NULL; + + return pCP; +} + +const CuePoint* Cues::GetLast() const { + if (m_cue_points == NULL || m_count <= 0) + return NULL; + + const long index = m_count - 1; + + CuePoint* const* const pp = m_cue_points; + if (pp == NULL) + return NULL; + + CuePoint* const pCP = pp[index]; + if (pCP == NULL || pCP->GetTimeCode() < 0) + return NULL; + + return pCP; +} + +const CuePoint* Cues::GetNext(const CuePoint* pCurr) const { + if (pCurr == NULL || pCurr->GetTimeCode() < 0 || m_cue_points == NULL || + m_count < 1) { + return NULL; + } + + long index = pCurr->m_index; + if (index >= m_count) + return NULL; + + CuePoint* const* const pp = m_cue_points; + if (pp == NULL || pp[index] != pCurr) + return NULL; + + ++index; + + if (index >= m_count) + return NULL; + + CuePoint* const pNext = pp[index]; + + if (pNext == NULL || pNext->GetTimeCode() < 0) + return NULL; + + return pNext; +} + +const BlockEntry* Cues::GetBlock(const CuePoint* pCP, + const CuePoint::TrackPosition* pTP) const { + if (pCP == NULL || pTP == NULL) + return NULL; + + return m_pSegment->GetBlock(*pCP, *pTP); +} + +const BlockEntry* Segment::GetBlock(const CuePoint& cp, + const CuePoint::TrackPosition& tp) { + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + // const long long pos_ = pCluster->m_pos; + // assert(pos_); + // const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < tp.m_pos) + i = k + 1; + else if (pos > tp.m_pos) + j = k; + else + return pCluster->GetEntry(cp, tp); + } + + assert(i == j); + // assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create(this, -1, tp.m_pos); //, -1); + if (pCluster == NULL) + return NULL; + + const ptrdiff_t idx = i - m_clusters; + + if (!PreloadCluster(pCluster, idx)) { + delete pCluster; + return NULL; + } + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster->GetEntry(cp, tp); +} + +const Cluster* Segment::FindOrPreloadCluster(long long requested_pos) { + if (requested_pos < 0) + return 0; + + Cluster** const ii = m_clusters; + Cluster** i = ii; + + const long count = m_clusterCount + m_clusterPreloadCount; + + Cluster** const jj = ii + count; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[ii, i) < pTP->m_pos + //[i, j) ? + //[j, jj) > pTP->m_pos + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pCluster = *k; + assert(pCluster); + + // const long long pos_ = pCluster->m_pos; + // assert(pos_); + // const long long pos = pos_ * ((pos_ < 0) ? -1 : 1); + + const long long pos = pCluster->GetPosition(); + assert(pos >= 0); + + if (pos < requested_pos) + i = k + 1; + else if (pos > requested_pos) + j = k; + else + return pCluster; + } + + assert(i == j); + // assert(Cluster::HasBlockEntries(this, tp.m_pos)); + + Cluster* const pCluster = Cluster::Create(this, -1, requested_pos); + if (pCluster == NULL) + return NULL; + + const ptrdiff_t idx = i - m_clusters; + + if (!PreloadCluster(pCluster, idx)) { + delete pCluster; + return NULL; + } + assert(m_clusters); + assert(m_clusterPreloadCount > 0); + assert(m_clusters[idx] == pCluster); + + return pCluster; +} + +CuePoint::CuePoint(long idx, long long pos) + : m_element_start(0), + m_element_size(0), + m_index(idx), + m_timecode(-1 * pos), + m_track_positions(NULL), + m_track_positions_count(0) { + assert(pos > 0); +} + +CuePoint::~CuePoint() { delete[] m_track_positions; } + +bool CuePoint::Load(IMkvReader* pReader) { + // odbgstream os; + // os << "CuePoint::Load(begin): timecode=" << m_timecode << endl; + + if (m_timecode >= 0) // already loaded + return true; + + assert(m_track_positions == NULL); + assert(m_track_positions_count == 0); + + long long pos_ = -m_timecode; + const long long element_start = pos_; + + long long stop; + + { + long len; + + const long long id = ReadID(pReader, pos_, len); + if (id != libwebm::kMkvCuePoint) + return false; + + pos_ += len; // consume ID + + const long long size = ReadUInt(pReader, pos_, len); + assert(size >= 0); + + pos_ += len; // consume Size field + // pos_ now points to start of payload + + stop = pos_ + size; + } + + const long long element_size = stop - element_start; + + long long pos = pos_; + + // First count number of track positions + + while (pos < stop) { + long len; + + const long long id = ReadID(pReader, pos, len); + if ((id < 0) || (pos + len > stop)) { + return false; + } + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + if ((size < 0) || (pos + len > stop)) { + return false; + } + + pos += len; // consume Size field + if ((pos + size) > stop) { + return false; + } + + if (id == libwebm::kMkvCueTime) + m_timecode = UnserializeUInt(pReader, pos, size); + + else if (id == libwebm::kMkvCueTrackPositions) + ++m_track_positions_count; + + pos += size; // consume payload + } + + if (m_timecode < 0 || m_track_positions_count <= 0) { + return false; + } + + // os << "CuePoint::Load(cont'd): idpos=" << idpos + // << " timecode=" << m_timecode + // << endl; + + m_track_positions = new (std::nothrow) TrackPosition[m_track_positions_count]; + if (m_track_positions == NULL) + return false; + + // Now parse track positions + + TrackPosition* p = m_track_positions; + pos = pos_; + + while (pos < stop) { + long len; + + const long long id = ReadID(pReader, pos, len); + if (id < 0 || (pos + len) > stop) + return false; + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); + assert((pos + len) <= stop); + + pos += len; // consume Size field + assert((pos + size) <= stop); + + if (id == libwebm::kMkvCueTrackPositions) { + TrackPosition& tp = *p++; + if (!tp.Parse(pReader, pos, size)) { + return false; + } + } + + pos += size; // consume payload + if (pos > stop) + return false; + } + + assert(size_t(p - m_track_positions) == m_track_positions_count); + + m_element_start = element_start; + m_element_size = element_size; + + return true; +} + +bool CuePoint::TrackPosition::Parse(IMkvReader* pReader, long long start_, + long long size_) { + const long long stop = start_ + size_; + long long pos = start_; + + m_track = -1; + m_pos = -1; + m_block = 1; // default + + while (pos < stop) { + long len; + + const long long id = ReadID(pReader, pos, len); + if ((id < 0) || ((pos + len) > stop)) { + return false; + } + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + if ((size < 0) || ((pos + len) > stop)) { + return false; + } + + pos += len; // consume Size field + if ((pos + size) > stop) { + return false; + } + + if (id == libwebm::kMkvCueTrack) + m_track = UnserializeUInt(pReader, pos, size); + else if (id == libwebm::kMkvCueClusterPosition) + m_pos = UnserializeUInt(pReader, pos, size); + else if (id == libwebm::kMkvCueBlockNumber) + m_block = UnserializeUInt(pReader, pos, size); + + pos += size; // consume payload + } + + if ((m_pos < 0) || (m_track <= 0)) { + return false; + } + + return true; +} + +const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const { + if (pTrack == NULL) { + return NULL; + } + + const long long n = pTrack->GetNumber(); + + const TrackPosition* i = m_track_positions; + const TrackPosition* const j = i + m_track_positions_count; + + while (i != j) { + const TrackPosition& p = *i++; + + if (p.m_track == n) + return &p; + } + + return NULL; // no matching track number found +} + +long long CuePoint::GetTimeCode() const { return m_timecode; } + +long long CuePoint::GetTime(const Segment* pSegment) const { + assert(pSegment); + assert(m_timecode >= 0); + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long time = scale * m_timecode; + + return time; +} + +bool Segment::DoneParsing() const { + if (m_size < 0) { + long long total, avail; + + const int status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return true; // must assume done + + if (total < 0) + return false; // assume live stream + + return (m_pos >= total); + } + + const long long stop = m_start + m_size; + + return (m_pos >= stop); +} + +const Cluster* Segment::GetFirst() const { + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + + return pCluster; +} + +const Cluster* Segment::GetLast() const { + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + const long idx = m_clusterCount - 1; + + Cluster* const pCluster = m_clusters[idx]; + assert(pCluster); + + return pCluster; +} + +unsigned long Segment::GetCount() const { return m_clusterCount; } + +const Cluster* Segment::GetNext(const Cluster* pCurr) { + assert(pCurr); + assert(pCurr != &m_eos); + assert(m_clusters); + + long idx = pCurr->m_index; + + if (idx >= 0) { + assert(m_clusterCount > 0); + assert(idx < m_clusterCount); + assert(pCurr == m_clusters[idx]); + + ++idx; + + if (idx >= m_clusterCount) + return &m_eos; // caller will LoadCluster as desired + + Cluster* const pNext = m_clusters[idx]; + assert(pNext); + assert(pNext->m_index >= 0); + assert(pNext->m_index == idx); + + return pNext; + } + + assert(m_clusterPreloadCount > 0); + + long long pos = pCurr->m_element_start; + + assert(m_size >= 0); // TODO + const long long stop = m_start + m_size; // end of segment + + { + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); // TODO + if (result != 0) + return NULL; + + const long long id = ReadID(m_pReader, pos, len); + if (id != libwebm::kMkvCluster) + return NULL; + + pos += len; // consume ID + + // Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); // TODO + assert((pos + len) <= stop); // TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size > 0); // TODO + // assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); + + pos += len; // consume length of size of element + assert((pos + size) <= stop); // TODO + + // Pos now points to start of payload + + pos += size; // consume payload + } + + long long off_next = 0; + + while (pos < stop) { + long len; + + long long result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); + assert((pos + len) <= stop); // TODO + if (result != 0) + return NULL; + + const long long idpos = pos; // pos of next (potential) cluster + + const long long id = ReadID(m_pReader, idpos, len); + if (id < 0) + return NULL; + + pos += len; // consume ID + + // Read Size + result = GetUIntLength(m_pReader, pos, len); + assert(result == 0); // TODO + assert((pos + len) <= stop); // TODO + + const long long size = ReadUInt(m_pReader, pos, len); + assert(size >= 0); // TODO + + pos += len; // consume length of size of element + assert((pos + size) <= stop); // TODO + + // Pos now points to start of payload + + if (size == 0) // weird + continue; + + if (id == libwebm::kMkvCluster) { + const long long off_next_ = idpos - m_start; + + long long pos_; + long len_; + + const long status = Cluster::HasBlockEntries(this, off_next_, pos_, len_); + + assert(status >= 0); + + if (status > 0) { + off_next = off_next_; + break; + } + } + + pos += size; // consume payload + } + + if (off_next <= 0) + return 0; + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + // const long long pos_ = pNext->m_pos; + // assert(pos_); + // pos = pos_ * ((pos_ < 0) ? -1 : 1); + + pos = pNext->GetPosition(); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else + return pNext; + } + + assert(i == j); + + Cluster* const pNext = Cluster::Create(this, -1, off_next); + if (pNext == NULL) + return NULL; + + const ptrdiff_t idx_next = i - m_clusters; // insertion position + + if (!PreloadCluster(pNext, idx_next)) { + delete pNext; + return NULL; + } + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + return pNext; +} + +long Segment::ParseNext(const Cluster* pCurr, const Cluster*& pResult, + long long& pos, long& len) { + assert(pCurr); + assert(!pCurr->EOS()); + assert(m_clusters); + + pResult = 0; + + if (pCurr->m_index >= 0) { // loaded (not merely preloaded) + assert(m_clusters[pCurr->m_index] == pCurr); + + const long next_idx = pCurr->m_index + 1; + + if (next_idx < m_clusterCount) { + pResult = m_clusters[next_idx]; + return 0; // success + } + + // curr cluster is last among loaded + + const long result = LoadCluster(pos, len); + + if (result < 0) // error or underflow + return result; + + if (result > 0) // no more clusters + { + // pResult = &m_eos; + return 1; + } + + pResult = GetLast(); + return 0; // success + } + + assert(m_pos > 0); + + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + // interrogate curr cluster + + pos = pCurr->m_element_start; + + if (pCurr->m_element_size >= 0) + pos += pCurr->m_element_size; + else { + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadUInt(m_pReader, pos, len); + + if (id != libwebm::kMkvCluster) + return -1; + + pos += len; // consume ID + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + pos += len; // consume size field + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) // TODO: should never happen + return E_FILE_FORMAT_INVALID; // TODO: resolve this + + // assert((pCurr->m_size <= 0) || (pCurr->m_size == size)); + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + // Pos now points to start of payload + + pos += size; // consume payload (that is, the current cluster) + if (segment_stop >= 0 && pos > segment_stop) + return E_FILE_FORMAT_INVALID; + + // By consuming the payload, we are assuming that the curr + // cluster isn't interesting. That is, we don't bother checking + // whether the payload of the curr cluster is less than what + // happens to be available (obtained via IMkvReader::Length). + // Presumably the caller has already dispensed with the current + // cluster, and really does want the next cluster. + } + + // pos now points to just beyond the last fully-loaded cluster + + for (;;) { + const long status = DoParseNext(pResult, pos, len); + + if (status <= 1) + return status; + } +} + +long Segment::DoParseNext(const Cluster*& pResult, long long& pos, long& len) { + long long total, avail; + + long status = m_pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size; + + // Parse next cluster. This is strictly a parsing activity. + // Creation of a new cluster object happens later, after the + // parsing is done. + + long long off_next = 0; + long long cluster_size = -1; + + for (;;) { + if ((total >= 0) && (pos >= total)) + return 1; // EOF + + if ((segment_stop >= 0) && (pos >= segment_stop)) + return 1; // EOF + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; // absolute + const long long idoff = pos - m_start; // relative + + const long long id = ReadID(m_pReader, idpos, len); // absolute + + if (id < 0) // error + return static_cast<long>(id); + + if (id == 0) // weird + return -1; // generic error + + pos += len; // consume ID + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + pos += len; // consume length of size of element + + // Pos now points to start of payload + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if ((segment_stop >= 0) && (size != unknown_size) && + ((pos + size) > segment_stop)) { + return E_FILE_FORMAT_INVALID; + } + + if (id == libwebm::kMkvCues) { + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + const long long element_stop = pos + size; + + if ((segment_stop >= 0) && (element_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + const long long element_start = idpos; + const long long element_size = element_stop - element_start; + + if (m_pCues == NULL) { + m_pCues = new (std::nothrow) + Cues(this, pos, size, element_start, element_size); + if (m_pCues == NULL) + return false; + } + + pos += size; // consume payload + if (segment_stop >= 0 && pos > segment_stop) + return E_FILE_FORMAT_INVALID; + + continue; + } + + if (id != libwebm::kMkvCluster) { // not a Cluster ID + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += size; // consume payload + if (segment_stop >= 0 && pos > segment_stop) + return E_FILE_FORMAT_INVALID; + + continue; + } + + // We have a cluster. + off_next = idoff; + + if (size != unknown_size) + cluster_size = size; + + break; + } + + assert(off_next > 0); // have cluster + + // We have parsed the next cluster. + // We have not created a cluster object yet. What we need + // to do now is determine whether it has already be preloaded + //(in which case, an object for this cluster has already been + // created), and if not, create a new cluster object. + + Cluster** const ii = m_clusters + m_clusterCount; + Cluster** i = ii; + + Cluster** const jj = ii + m_clusterPreloadCount; + Cluster** j = jj; + + while (i < j) { + // INVARIANT: + //[0, i) < pos_next + //[i, j) ? + //[j, jj) > pos_next + + Cluster** const k = i + (j - i) / 2; + assert(k < jj); + + const Cluster* const pNext = *k; + assert(pNext); + assert(pNext->m_index < 0); + + pos = pNext->GetPosition(); + assert(pos >= 0); + + if (pos < off_next) + i = k + 1; + else if (pos > off_next) + j = k; + else { + pResult = pNext; + return 0; // success + } + } + + assert(i == j); + + long long pos_; + long len_; + + status = Cluster::HasBlockEntries(this, off_next, pos_, len_); + + if (status < 0) { // error or underflow + pos = pos_; + len = len_; + + return status; + } + + if (status > 0) { // means "found at least one block entry" + Cluster* const pNext = Cluster::Create(this, + -1, // preloaded + off_next); + if (pNext == NULL) + return -1; + + const ptrdiff_t idx_next = i - m_clusters; // insertion position + + if (!PreloadCluster(pNext, idx_next)) { + delete pNext; + return -1; + } + assert(m_clusters); + assert(idx_next < m_clusterSize); + assert(m_clusters[idx_next] == pNext); + + pResult = pNext; + return 0; // success + } + + // status == 0 means "no block entries found" + + if (cluster_size < 0) { // unknown size + const long long payload_pos = pos; // absolute pos of cluster payload + + for (;;) { // determine cluster size + if ((total >= 0) && (pos >= total)) + break; + + if ((segment_stop >= 0) && (pos >= segment_stop)) + break; // no more clusters + + // Read ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long idpos = pos; + const long long id = ReadID(m_pReader, idpos, len); + + if (id < 0) // error (or underflow) + return static_cast<long>(id); + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if (id == libwebm::kMkvCluster || id == libwebm::kMkvCues) + break; + + pos += len; // consume ID (of sub-element) + + // Read Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(m_pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(m_pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + pos += len; // consume size field of element + + // pos now points to start of sub-element's payload + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; // not allowed for sub-elements + + if ((segment_stop >= 0) && ((pos + size) > segment_stop)) // weird + return E_FILE_FORMAT_INVALID; + + pos += size; // consume payload of sub-element + if (segment_stop >= 0 && pos > segment_stop) + return E_FILE_FORMAT_INVALID; + } // determine cluster size + + cluster_size = pos - payload_pos; + assert(cluster_size >= 0); // TODO: handle cluster_size = 0 + + pos = payload_pos; // reset and re-parse original cluster + } + + pos += cluster_size; // consume payload + if (segment_stop >= 0 && pos > segment_stop) + return E_FILE_FORMAT_INVALID; + + return 2; // try to find a cluster that follows next +} + +const Cluster* Segment::FindCluster(long long time_ns) const { + if ((m_clusters == NULL) || (m_clusterCount <= 0)) + return &m_eos; + + { + Cluster* const pCluster = m_clusters[0]; + assert(pCluster); + assert(pCluster->m_index == 0); + + if (time_ns <= pCluster->GetTime()) + return pCluster; + } + + // Binary search of cluster array + + long i = 0; + long j = m_clusterCount; + + while (i < j) { + // INVARIANT: + //[0, i) <= time_ns + //[i, j) ? + //[j, m_clusterCount) > time_ns + + const long k = i + (j - i) / 2; + assert(k < m_clusterCount); + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + i = k + 1; + else + j = k; + + assert(i <= j); + } + + assert(i == j); + assert(i > 0); + assert(i <= m_clusterCount); + + const long k = i - 1; + + Cluster* const pCluster = m_clusters[k]; + assert(pCluster); + assert(pCluster->m_index == k); + assert(pCluster->GetTime() <= time_ns); + + return pCluster; +} + +const Tracks* Segment::GetTracks() const { return m_pTracks; } +const SegmentInfo* Segment::GetInfo() const { return m_pInfo; } +const Cues* Segment::GetCues() const { return m_pCues; } +const Chapters* Segment::GetChapters() const { return m_pChapters; } +const Tags* Segment::GetTags() const { return m_pTags; } +const SeekHead* Segment::GetSeekHead() const { return m_pSeekHead; } + +long long Segment::GetDuration() const { + assert(m_pInfo); + return m_pInfo->GetDuration(); +} + +Chapters::Chapters(Segment* pSegment, long long payload_start, + long long payload_size, long long element_start, + long long element_size) + : m_pSegment(pSegment), + m_start(payload_start), + m_size(payload_size), + m_element_start(element_start), + m_element_size(element_size), + m_editions(NULL), + m_editions_size(0), + m_editions_count(0) {} + +Chapters::~Chapters() { + while (m_editions_count > 0) { + Edition& e = m_editions[--m_editions_count]; + e.Clear(); + } + delete[] m_editions; +} + +long Chapters::Parse() { + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; // payload start + const long long stop = pos + m_size; // payload stop + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == libwebm::kMkvEditionEntry) { + status = ParseEdition(pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + return 0; +} + +int Chapters::GetEditionCount() const { return m_editions_count; } + +const Chapters::Edition* Chapters::GetEdition(int idx) const { + if (idx < 0) + return NULL; + + if (idx >= m_editions_count) + return NULL; + + return m_editions + idx; +} + +bool Chapters::ExpandEditionsArray() { + if (m_editions_size > m_editions_count) + return true; // nothing else to do + + const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size; + + Edition* const editions = new (std::nothrow) Edition[size]; + + if (editions == NULL) + return false; + + for (int idx = 0; idx < m_editions_count; ++idx) { + m_editions[idx].ShallowCopy(editions[idx]); + } + + delete[] m_editions; + m_editions = editions; + + m_editions_size = size; + return true; +} + +long Chapters::ParseEdition(long long pos, long long size) { + if (!ExpandEditionsArray()) + return -1; + + Edition& e = m_editions[m_editions_count++]; + e.Init(); + + return e.Parse(m_pSegment->m_pReader, pos, size); +} + +Chapters::Edition::Edition() {} + +Chapters::Edition::~Edition() {} + +int Chapters::Edition::GetAtomCount() const { return m_atoms_count; } + +const Chapters::Atom* Chapters::Edition::GetAtom(int index) const { + if (index < 0) + return NULL; + + if (index >= m_atoms_count) + return NULL; + + return m_atoms + index; +} + +void Chapters::Edition::Init() { + m_atoms = NULL; + m_atoms_size = 0; + m_atoms_count = 0; +} + +void Chapters::Edition::ShallowCopy(Edition& rhs) const { + rhs.m_atoms = m_atoms; + rhs.m_atoms_size = m_atoms_size; + rhs.m_atoms_count = m_atoms_count; +} + +void Chapters::Edition::Clear() { + while (m_atoms_count > 0) { + Atom& a = m_atoms[--m_atoms_count]; + a.Clear(); + } + + delete[] m_atoms; + m_atoms = NULL; + + m_atoms_size = 0; +} + +long Chapters::Edition::Parse(IMkvReader* pReader, long long pos, + long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) + continue; + + if (id == libwebm::kMkvChapterAtom) { + status = ParseAtom(pReader, pos, size); + + if (status < 0) // error + return status; + } + + pos += size; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + return 0; +} + +long Chapters::Edition::ParseAtom(IMkvReader* pReader, long long pos, + long long size) { + if (!ExpandAtomsArray()) + return -1; + + Atom& a = m_atoms[m_atoms_count++]; + a.Init(); + + return a.Parse(pReader, pos, size); +} + +bool Chapters::Edition::ExpandAtomsArray() { + if (m_atoms_size > m_atoms_count) + return true; // nothing else to do + + const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size; + + Atom* const atoms = new (std::nothrow) Atom[size]; + + if (atoms == NULL) + return false; + + for (int idx = 0; idx < m_atoms_count; ++idx) { + m_atoms[idx].ShallowCopy(atoms[idx]); + } + + delete[] m_atoms; + m_atoms = atoms; + + m_atoms_size = size; + return true; +} + +Chapters::Atom::Atom() {} + +Chapters::Atom::~Atom() {} + +unsigned long long Chapters::Atom::GetUID() const { return m_uid; } + +const char* Chapters::Atom::GetStringUID() const { return m_string_uid; } + +long long Chapters::Atom::GetStartTimecode() const { return m_start_timecode; } + +long long Chapters::Atom::GetStopTimecode() const { return m_stop_timecode; } + +long long Chapters::Atom::GetStartTime(const Chapters* pChapters) const { + return GetTime(pChapters, m_start_timecode); +} + +long long Chapters::Atom::GetStopTime(const Chapters* pChapters) const { + return GetTime(pChapters, m_stop_timecode); +} + +int Chapters::Atom::GetDisplayCount() const { return m_displays_count; } + +const Chapters::Display* Chapters::Atom::GetDisplay(int index) const { + if (index < 0) + return NULL; + + if (index >= m_displays_count) + return NULL; + + return m_displays + index; +} + +void Chapters::Atom::Init() { + m_string_uid = NULL; + m_uid = 0; + m_start_timecode = -1; + m_stop_timecode = -1; + + m_displays = NULL; + m_displays_size = 0; + m_displays_count = 0; +} + +void Chapters::Atom::ShallowCopy(Atom& rhs) const { + rhs.m_string_uid = m_string_uid; + rhs.m_uid = m_uid; + rhs.m_start_timecode = m_start_timecode; + rhs.m_stop_timecode = m_stop_timecode; + + rhs.m_displays = m_displays; + rhs.m_displays_size = m_displays_size; + rhs.m_displays_count = m_displays_count; +} + +void Chapters::Atom::Clear() { + delete[] m_string_uid; + m_string_uid = NULL; + + while (m_displays_count > 0) { + Display& d = m_displays[--m_displays_count]; + d.Clear(); + } + + delete[] m_displays; + m_displays = NULL; + + m_displays_size = 0; +} + +long Chapters::Atom::Parse(IMkvReader* pReader, long long pos, long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // 0 length payload, skip. + continue; + + if (id == libwebm::kMkvChapterDisplay) { + status = ParseDisplay(pReader, pos, size); + + if (status < 0) // error + return status; + } else if (id == libwebm::kMkvChapterStringUID) { + status = UnserializeString(pReader, pos, size, m_string_uid); + + if (status < 0) // error + return status; + } else if (id == libwebm::kMkvChapterUID) { + long long val; + status = UnserializeInt(pReader, pos, size, val); + + if (status < 0) // error + return status; + + m_uid = static_cast<unsigned long long>(val); + } else if (id == libwebm::kMkvChapterTimeStart) { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast<long>(val); + + m_start_timecode = val; + } else if (id == libwebm::kMkvChapterTimeEnd) { + const long long val = UnserializeUInt(pReader, pos, size); + + if (val < 0) // error + return static_cast<long>(val); + + m_stop_timecode = val; + } + + pos += size; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + return 0; +} + +long long Chapters::Atom::GetTime(const Chapters* pChapters, + long long timecode) { + if (pChapters == NULL) + return -1; + + Segment* const pSegment = pChapters->m_pSegment; + + if (pSegment == NULL) // weird + return -1; + + const SegmentInfo* const pInfo = pSegment->GetInfo(); + + if (pInfo == NULL) + return -1; + + const long long timecode_scale = pInfo->GetTimeCodeScale(); + + if (timecode_scale < 1) // weird + return -1; + + if (timecode < 0) + return -1; + + const long long result = timecode_scale * timecode; + + return result; +} + +long Chapters::Atom::ParseDisplay(IMkvReader* pReader, long long pos, + long long size) { + if (!ExpandDisplaysArray()) + return -1; + + Display& d = m_displays[m_displays_count++]; + d.Init(); + + return d.Parse(pReader, pos, size); +} + +bool Chapters::Atom::ExpandDisplaysArray() { + if (m_displays_size > m_displays_count) + return true; // nothing else to do + + const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size; + + Display* const displays = new (std::nothrow) Display[size]; + + if (displays == NULL) + return false; + + for (int idx = 0; idx < m_displays_count; ++idx) { + m_displays[idx].ShallowCopy(displays[idx]); + } + + delete[] m_displays; + m_displays = displays; + + m_displays_size = size; + return true; +} + +Chapters::Display::Display() {} + +Chapters::Display::~Display() {} + +const char* Chapters::Display::GetString() const { return m_string; } + +const char* Chapters::Display::GetLanguage() const { return m_language; } + +const char* Chapters::Display::GetCountry() const { return m_country; } + +void Chapters::Display::Init() { + m_string = NULL; + m_language = NULL; + m_country = NULL; +} + +void Chapters::Display::ShallowCopy(Display& rhs) const { + rhs.m_string = m_string; + rhs.m_language = m_language; + rhs.m_country = m_country; +} + +void Chapters::Display::Clear() { + delete[] m_string; + m_string = NULL; + + delete[] m_language; + m_language = NULL; + + delete[] m_country; + m_country = NULL; +} + +long Chapters::Display::Parse(IMkvReader* pReader, long long pos, + long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // No payload. + continue; + + if (id == libwebm::kMkvChapString) { + status = UnserializeString(pReader, pos, size, m_string); + + if (status) + return status; + } else if (id == libwebm::kMkvChapLanguage) { + status = UnserializeString(pReader, pos, size, m_language); + + if (status) + return status; + } else if (id == libwebm::kMkvChapCountry) { + status = UnserializeString(pReader, pos, size, m_country); + + if (status) + return status; + } + + pos += size; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + return 0; +} + +Tags::Tags(Segment* pSegment, long long payload_start, long long payload_size, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(payload_start), + m_size(payload_size), + m_element_start(element_start), + m_element_size(element_size), + m_tags(NULL), + m_tags_size(0), + m_tags_count(0) {} + +Tags::~Tags() { + while (m_tags_count > 0) { + Tag& t = m_tags[--m_tags_count]; + t.Clear(); + } + delete[] m_tags; +} + +long Tags::Parse() { + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; // payload start + const long long stop = pos + m_size; // payload stop + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) + return status; + + if (size == 0) // 0 length tag, read another + continue; + + if (id == libwebm::kMkvTag) { + status = ParseTag(pos, size); + + if (status < 0) + return status; + } + + pos += size; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +int Tags::GetTagCount() const { return m_tags_count; } + +const Tags::Tag* Tags::GetTag(int idx) const { + if (idx < 0) + return NULL; + + if (idx >= m_tags_count) + return NULL; + + return m_tags + idx; +} + +bool Tags::ExpandTagsArray() { + if (m_tags_size > m_tags_count) + return true; // nothing else to do + + const int size = (m_tags_size == 0) ? 1 : 2 * m_tags_size; + + Tag* const tags = new (std::nothrow) Tag[size]; + + if (tags == NULL) + return false; + + for (int idx = 0; idx < m_tags_count; ++idx) { + m_tags[idx].ShallowCopy(tags[idx]); + } + + delete[] m_tags; + m_tags = tags; + + m_tags_size = size; + return true; +} + +long Tags::ParseTag(long long pos, long long size) { + if (!ExpandTagsArray()) + return -1; + + Tag& t = m_tags[m_tags_count++]; + t.Init(); + + return t.Parse(m_pSegment->m_pReader, pos, size); +} + +Tags::Tag::Tag() {} + +Tags::Tag::~Tag() {} + +int Tags::Tag::GetSimpleTagCount() const { return m_simple_tags_count; } + +const Tags::SimpleTag* Tags::Tag::GetSimpleTag(int index) const { + if (index < 0) + return NULL; + + if (index >= m_simple_tags_count) + return NULL; + + return m_simple_tags + index; +} + +void Tags::Tag::Init() { + m_simple_tags = NULL; + m_simple_tags_size = 0; + m_simple_tags_count = 0; +} + +void Tags::Tag::ShallowCopy(Tag& rhs) const { + rhs.m_simple_tags = m_simple_tags; + rhs.m_simple_tags_size = m_simple_tags_size; + rhs.m_simple_tags_count = m_simple_tags_count; +} + +void Tags::Tag::Clear() { + while (m_simple_tags_count > 0) { + SimpleTag& d = m_simple_tags[--m_simple_tags_count]; + d.Clear(); + } + + delete[] m_simple_tags; + m_simple_tags = NULL; + + m_simple_tags_size = 0; +} + +long Tags::Tag::Parse(IMkvReader* pReader, long long pos, long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) + return status; + + if (size == 0) // 0 length tag, read another + continue; + + if (id == libwebm::kMkvSimpleTag) { + status = ParseSimpleTag(pReader, pos, size); + + if (status < 0) + return status; + } + + pos += size; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + return 0; +} + +long Tags::Tag::ParseSimpleTag(IMkvReader* pReader, long long pos, + long long size) { + if (!ExpandSimpleTagsArray()) + return -1; + + SimpleTag& st = m_simple_tags[m_simple_tags_count++]; + st.Init(); + + return st.Parse(pReader, pos, size); +} + +bool Tags::Tag::ExpandSimpleTagsArray() { + if (m_simple_tags_size > m_simple_tags_count) + return true; // nothing else to do + + const int size = (m_simple_tags_size == 0) ? 1 : 2 * m_simple_tags_size; + + SimpleTag* const displays = new (std::nothrow) SimpleTag[size]; + + if (displays == NULL) + return false; + + for (int idx = 0; idx < m_simple_tags_count; ++idx) { + m_simple_tags[idx].ShallowCopy(displays[idx]); + } + + delete[] m_simple_tags; + m_simple_tags = displays; + + m_simple_tags_size = size; + return true; +} + +Tags::SimpleTag::SimpleTag() {} + +Tags::SimpleTag::~SimpleTag() {} + +const char* Tags::SimpleTag::GetTagName() const { return m_tag_name; } + +const char* Tags::SimpleTag::GetTagString() const { return m_tag_string; } + +void Tags::SimpleTag::Init() { + m_tag_name = NULL; + m_tag_string = NULL; +} + +void Tags::SimpleTag::ShallowCopy(SimpleTag& rhs) const { + rhs.m_tag_name = m_tag_name; + rhs.m_tag_string = m_tag_string; +} + +void Tags::SimpleTag::Clear() { + delete[] m_tag_name; + m_tag_name = NULL; + + delete[] m_tag_string; + m_tag_string = NULL; +} + +long Tags::SimpleTag::Parse(IMkvReader* pReader, long long pos, + long long size) { + const long long stop = pos + size; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == libwebm::kMkvTagName) { + status = UnserializeString(pReader, pos, size, m_tag_name); + + if (status) + return status; + } else if (id == libwebm::kMkvTagString) { + status = UnserializeString(pReader, pos, size, m_tag_string); + + if (status) + return status; + } + + pos += size; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + return 0; +} + +SegmentInfo::SegmentInfo(Segment* pSegment, long long start, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_pMuxingAppAsUTF8(NULL), + m_pWritingAppAsUTF8(NULL), + m_pTitleAsUTF8(NULL) {} + +SegmentInfo::~SegmentInfo() { + delete[] m_pMuxingAppAsUTF8; + m_pMuxingAppAsUTF8 = NULL; + + delete[] m_pWritingAppAsUTF8; + m_pWritingAppAsUTF8 = NULL; + + delete[] m_pTitleAsUTF8; + m_pTitleAsUTF8 = NULL; +} + +long SegmentInfo::Parse() { + assert(m_pMuxingAppAsUTF8 == NULL); + assert(m_pWritingAppAsUTF8 == NULL); + assert(m_pTitleAsUTF8 == NULL); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = m_start; + const long long stop = m_start + m_size; + + m_timecodeScale = 1000000; + m_duration = -1; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == libwebm::kMkvTimecodeScale) { + m_timecodeScale = UnserializeUInt(pReader, pos, size); + + if (m_timecodeScale <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvDuration) { + const long status = UnserializeFloat(pReader, pos, size, m_duration); + + if (status < 0) + return status; + + if (m_duration < 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvMuxingApp) { + const long status = + UnserializeString(pReader, pos, size, m_pMuxingAppAsUTF8); + + if (status) + return status; + } else if (id == libwebm::kMkvWritingApp) { + const long status = + UnserializeString(pReader, pos, size, m_pWritingAppAsUTF8); + + if (status) + return status; + } else if (id == libwebm::kMkvTitle) { + const long status = UnserializeString(pReader, pos, size, m_pTitleAsUTF8); + + if (status) + return status; + } + + pos += size; + + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + const double rollover_check = m_duration * m_timecodeScale; + if (rollover_check > static_cast<double>(LLONG_MAX)) + return E_FILE_FORMAT_INVALID; + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +long long SegmentInfo::GetTimeCodeScale() const { return m_timecodeScale; } + +long long SegmentInfo::GetDuration() const { + if (m_duration < 0) + return -1; + + assert(m_timecodeScale >= 1); + + const double dd = double(m_duration) * double(m_timecodeScale); + const long long d = static_cast<long long>(dd); + + return d; +} + +const char* SegmentInfo::GetMuxingAppAsUTF8() const { + return m_pMuxingAppAsUTF8; +} + +const char* SegmentInfo::GetWritingAppAsUTF8() const { + return m_pWritingAppAsUTF8; +} + +const char* SegmentInfo::GetTitleAsUTF8() const { return m_pTitleAsUTF8; } + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +ContentEncoding::ContentCompression::ContentCompression() + : algo(0), settings(NULL), settings_len(0) {} + +ContentEncoding::ContentCompression::~ContentCompression() { + delete[] settings; +} + +ContentEncoding::ContentEncryption::ContentEncryption() + : algo(0), + key_id(NULL), + key_id_len(0), + signature(NULL), + signature_len(0), + sig_key_id(NULL), + sig_key_id_len(0), + sig_algo(0), + sig_hash_algo(0) {} + +ContentEncoding::ContentEncryption::~ContentEncryption() { + delete[] key_id; + delete[] signature; + delete[] sig_key_id; +} + +ContentEncoding::ContentEncoding() + : compression_entries_(NULL), + compression_entries_end_(NULL), + encryption_entries_(NULL), + encryption_entries_end_(NULL), + encoding_order_(0), + encoding_scope_(1), + encoding_type_(0) {} + +ContentEncoding::~ContentEncoding() { + ContentCompression** comp_i = compression_entries_; + ContentCompression** const comp_j = compression_entries_end_; + + while (comp_i != comp_j) { + ContentCompression* const comp = *comp_i++; + delete comp; + } + + delete[] compression_entries_; + + ContentEncryption** enc_i = encryption_entries_; + ContentEncryption** const enc_j = encryption_entries_end_; + + while (enc_i != enc_j) { + ContentEncryption* const enc = *enc_i++; + delete enc; + } + + delete[] encryption_entries_; +} + +const ContentEncoding::ContentCompression* +ContentEncoding::GetCompressionByIndex(unsigned long idx) const { + const ptrdiff_t count = compression_entries_end_ - compression_entries_; + assert(count >= 0); + + if (idx >= static_cast<unsigned long>(count)) + return NULL; + + return compression_entries_[idx]; +} + +unsigned long ContentEncoding::GetCompressionCount() const { + const ptrdiff_t count = compression_entries_end_ - compression_entries_; + assert(count >= 0); + + return static_cast<unsigned long>(count); +} + +const ContentEncoding::ContentEncryption* ContentEncoding::GetEncryptionByIndex( + unsigned long idx) const { + const ptrdiff_t count = encryption_entries_end_ - encryption_entries_; + assert(count >= 0); + + if (idx >= static_cast<unsigned long>(count)) + return NULL; + + return encryption_entries_[idx]; +} + +unsigned long ContentEncoding::GetEncryptionCount() const { + const ptrdiff_t count = encryption_entries_end_ - encryption_entries_; + assert(count >= 0); + + return static_cast<unsigned long>(count); +} + +long ContentEncoding::ParseContentEncAESSettingsEntry( + long long start, long long size, IMkvReader* pReader, + ContentEncAESSettings* aes) { + assert(pReader); + assert(aes); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == libwebm::kMkvAESSettingsCipherMode) { + aes->cipher_mode = UnserializeUInt(pReader, pos, size); + if (aes->cipher_mode != 1) + return E_FILE_FORMAT_INVALID; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + return 0; +} + +long ContentEncoding::ParseContentEncodingEntry(long long start, long long size, + IMkvReader* pReader) { + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentCompression and ContentEncryption elements. + int compression_count = 0; + int encryption_count = 0; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == libwebm::kMkvContentCompression) + ++compression_count; + + if (id == libwebm::kMkvContentEncryption) + ++encryption_count; + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (compression_count <= 0 && encryption_count <= 0) + return -1; + + if (compression_count > 0) { + compression_entries_ = + new (std::nothrow) ContentCompression*[compression_count]; + if (!compression_entries_) + return -1; + compression_entries_end_ = compression_entries_; + } + + if (encryption_count > 0) { + encryption_entries_ = + new (std::nothrow) ContentEncryption*[encryption_count]; + if (!encryption_entries_) { + delete[] compression_entries_; + compression_entries_ = NULL; + return -1; + } + encryption_entries_end_ = encryption_entries_; + } + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == libwebm::kMkvContentEncodingOrder) { + encoding_order_ = UnserializeUInt(pReader, pos, size); + } else if (id == libwebm::kMkvContentEncodingScope) { + encoding_scope_ = UnserializeUInt(pReader, pos, size); + if (encoding_scope_ < 1) + return -1; + } else if (id == libwebm::kMkvContentEncodingType) { + encoding_type_ = UnserializeUInt(pReader, pos, size); + } else if (id == libwebm::kMkvContentCompression) { + ContentCompression* const compression = + new (std::nothrow) ContentCompression(); + if (!compression) + return -1; + + status = ParseCompressionEntry(pos, size, pReader, compression); + if (status) { + delete compression; + return status; + } + assert(compression_count > 0); + *compression_entries_end_++ = compression; + } else if (id == libwebm::kMkvContentEncryption) { + ContentEncryption* const encryption = + new (std::nothrow) ContentEncryption(); + if (!encryption) + return -1; + + status = ParseEncryptionEntry(pos, size, pReader, encryption); + if (status) { + delete encryption; + return status; + } + assert(encryption_count > 0); + *encryption_entries_end_++ = encryption; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + return 0; +} + +long ContentEncoding::ParseCompressionEntry(long long start, long long size, + IMkvReader* pReader, + ContentCompression* compression) { + assert(pReader); + assert(compression); + + long long pos = start; + const long long stop = start + size; + + bool valid = false; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == libwebm::kMkvContentCompAlgo) { + long long algo = UnserializeUInt(pReader, pos, size); + if (algo < 0) + return E_FILE_FORMAT_INVALID; + compression->algo = algo; + valid = true; + } else if (id == libwebm::kMkvContentCompSettings) { + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast<size_t>(size); + unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen); + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast<long>(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + // There should be only one settings element per content compression. + if (compression->settings != NULL) { + delete[] buf; + return E_FILE_FORMAT_INVALID; + } + + compression->settings = buf; + compression->settings_len = buflen; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + // ContentCompAlgo is mandatory + if (!valid) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +long ContentEncoding::ParseEncryptionEntry(long long start, long long size, + IMkvReader* pReader, + ContentEncryption* encryption) { + assert(pReader); + assert(encryption); + + long long pos = start; + const long long stop = start + size; + + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + if (id == libwebm::kMkvContentEncAlgo) { + encryption->algo = UnserializeUInt(pReader, pos, size); + if (encryption->algo != 5) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvContentEncKeyID) { + delete[] encryption->key_id; + encryption->key_id = NULL; + encryption->key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast<size_t>(size); + unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen); + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast<long>(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + encryption->key_id = buf; + encryption->key_id_len = buflen; + } else if (id == libwebm::kMkvContentSignature) { + delete[] encryption->signature; + encryption->signature = NULL; + encryption->signature_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast<size_t>(size); + unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen); + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast<long>(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + encryption->signature = buf; + encryption->signature_len = buflen; + } else if (id == libwebm::kMkvContentSigKeyID) { + delete[] encryption->sig_key_id; + encryption->sig_key_id = NULL; + encryption->sig_key_id_len = 0; + + if (size <= 0) + return E_FILE_FORMAT_INVALID; + + const size_t buflen = static_cast<size_t>(size); + unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen); + if (buf == NULL) + return -1; + + const int read_status = + pReader->Read(pos, static_cast<long>(buflen), buf); + if (read_status) { + delete[] buf; + return status; + } + + encryption->sig_key_id = buf; + encryption->sig_key_id_len = buflen; + } else if (id == libwebm::kMkvContentSigAlgo) { + encryption->sig_algo = UnserializeUInt(pReader, pos, size); + } else if (id == libwebm::kMkvContentSigHashAlgo) { + encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size); + } else if (id == libwebm::kMkvContentEncAESSettings) { + const long status = ParseContentEncAESSettingsEntry( + pos, size, pReader, &encryption->aes_settings); + if (status) + return status; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + return 0; +} + +Track::Track(Segment* pSegment, long long element_start, long long element_size) + : m_pSegment(pSegment), + m_element_start(element_start), + m_element_size(element_size), + content_encoding_entries_(NULL), + content_encoding_entries_end_(NULL) {} + +Track::~Track() { + Info& info = const_cast<Info&>(m_info); + info.Clear(); + + ContentEncoding** i = content_encoding_entries_; + ContentEncoding** const j = content_encoding_entries_end_; + + while (i != j) { + ContentEncoding* const encoding = *i++; + delete encoding; + } + + delete[] content_encoding_entries_; +} + +long Track::Create(Segment* pSegment, const Info& info, long long element_start, + long long element_size, Track*& pResult) { + if (pResult) + return -1; + + Track* const pTrack = + new (std::nothrow) Track(pSegment, element_start, element_size); + + if (pTrack == NULL) + return -1; // generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) { // error + delete pTrack; + return status; + } + + pResult = pTrack; + return 0; // success +} + +Track::Info::Info() + : uid(0), + defaultDuration(0), + codecDelay(0), + seekPreRoll(0), + nameAsUTF8(NULL), + language(NULL), + codecId(NULL), + codecNameAsUTF8(NULL), + codecPrivate(NULL), + codecPrivateSize(0), + lacing(false) {} + +Track::Info::~Info() { Clear(); } + +void Track::Info::Clear() { + delete[] nameAsUTF8; + nameAsUTF8 = NULL; + + delete[] language; + language = NULL; + + delete[] codecId; + codecId = NULL; + + delete[] codecPrivate; + codecPrivate = NULL; + codecPrivateSize = 0; + + delete[] codecNameAsUTF8; + codecNameAsUTF8 = NULL; +} + +int Track::Info::CopyStr(char* Info::*str, Info& dst_) const { + if (str == static_cast<char * Info::*>(NULL)) + return -1; + + char*& dst = dst_.*str; + + if (dst) // should be NULL already + return -1; + + const char* const src = this->*str; + + if (src == NULL) + return 0; + + const size_t len = strlen(src); + + dst = SafeArrayAlloc<char>(1, len + 1); + + if (dst == NULL) + return -1; + + strcpy(dst, src); + + return 0; +} + +int Track::Info::Copy(Info& dst) const { + if (&dst == this) + return 0; + + dst.type = type; + dst.number = number; + dst.defaultDuration = defaultDuration; + dst.codecDelay = codecDelay; + dst.seekPreRoll = seekPreRoll; + dst.uid = uid; + dst.lacing = lacing; + dst.settings = settings; + + // We now copy the string member variables from src to dst. + // This involves memory allocation so in principle the operation + // can fail (indeed, that's why we have Info::Copy), so we must + // report this to the caller. An error return from this function + // therefore implies that the copy was only partially successful. + + if (int status = CopyStr(&Info::nameAsUTF8, dst)) + return status; + + if (int status = CopyStr(&Info::language, dst)) + return status; + + if (int status = CopyStr(&Info::codecId, dst)) + return status; + + if (int status = CopyStr(&Info::codecNameAsUTF8, dst)) + return status; + + if (codecPrivateSize > 0) { + if (codecPrivate == NULL) + return -1; + + if (dst.codecPrivate) + return -1; + + if (dst.codecPrivateSize != 0) + return -1; + + dst.codecPrivate = SafeArrayAlloc<unsigned char>(1, codecPrivateSize); + + if (dst.codecPrivate == NULL) + return -1; + + memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize); + dst.codecPrivateSize = codecPrivateSize; + } + + return 0; +} + +const BlockEntry* Track::GetEOS() const { return &m_eos; } + +long Track::GetType() const { return m_info.type; } + +long Track::GetNumber() const { return m_info.number; } + +unsigned long long Track::GetUid() const { return m_info.uid; } + +const char* Track::GetNameAsUTF8() const { return m_info.nameAsUTF8; } + +const char* Track::GetLanguage() const { return m_info.language; } + +const char* Track::GetCodecNameAsUTF8() const { return m_info.codecNameAsUTF8; } + +const char* Track::GetCodecId() const { return m_info.codecId; } + +const unsigned char* Track::GetCodecPrivate(size_t& size) const { + size = m_info.codecPrivateSize; + return m_info.codecPrivate; +} + +bool Track::GetLacing() const { return m_info.lacing; } + +unsigned long long Track::GetDefaultDuration() const { + return m_info.defaultDuration; +} + +unsigned long long Track::GetCodecDelay() const { return m_info.codecDelay; } + +unsigned long long Track::GetSeekPreRoll() const { return m_info.seekPreRoll; } + +long Track::GetFirst(const BlockEntry*& pBlockEntry) const { + const Cluster* pCluster = m_pSegment->GetFirst(); + + for (int i = 0;;) { + if (pCluster == NULL) { + pBlockEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) { + if (m_pSegment->DoneParsing()) { + pBlockEntry = GetEOS(); + return 1; + } + + pBlockEntry = 0; + return E_BUFFER_NOT_FULL; + } + + long status = pCluster->GetFirst(pBlockEntry); + + if (status < 0) // error + return status; + + if (pBlockEntry == 0) { // empty cluster + pCluster = m_pSegment->GetNext(pCluster); + continue; + } + + for (;;) { + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + + const long long tn = pBlock->GetTrackNumber(); + + if ((tn == m_info.number) && VetEntry(pBlockEntry)) + return 0; + + const BlockEntry* pNextEntry; + + status = pCluster->GetNext(pBlockEntry, pNextEntry); + + if (status < 0) // error + return status; + + if (pNextEntry == 0) + break; + + pBlockEntry = pNextEntry; + } + + ++i; + + if (i >= 100) + break; + + pCluster = m_pSegment->GetNext(pCluster); + } + + // NOTE: if we get here, it means that we didn't find a block with + // a matching track number. We interpret that as an error (which + // might be too conservative). + + pBlockEntry = GetEOS(); // so we can return a non-NULL value + return 1; +} + +long Track::GetNext(const BlockEntry* pCurrEntry, + const BlockEntry*& pNextEntry) const { + assert(pCurrEntry); + assert(!pCurrEntry->EOS()); //? + + const Block* const pCurrBlock = pCurrEntry->GetBlock(); + assert(pCurrBlock && pCurrBlock->GetTrackNumber() == m_info.number); + if (!pCurrBlock || pCurrBlock->GetTrackNumber() != m_info.number) + return -1; + + const Cluster* pCluster = pCurrEntry->GetCluster(); + assert(pCluster); + assert(!pCluster->EOS()); + + long status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) // error + return status; + + for (int i = 0;;) { + while (pNextEntry) { + const Block* const pNextBlock = pNextEntry->GetBlock(); + assert(pNextBlock); + + if (pNextBlock->GetTrackNumber() == m_info.number) + return 0; + + pCurrEntry = pNextEntry; + + status = pCluster->GetNext(pCurrEntry, pNextEntry); + + if (status < 0) // error + return status; + } + + pCluster = m_pSegment->GetNext(pCluster); + + if (pCluster == NULL) { + pNextEntry = GetEOS(); + return 1; + } + + if (pCluster->EOS()) { + if (m_pSegment->DoneParsing()) { + pNextEntry = GetEOS(); + return 1; + } + + // TODO: there is a potential O(n^2) problem here: we tell the + // caller to (pre)load another cluster, which he does, but then he + // calls GetNext again, which repeats the same search. This is + // a pathological case, since the only way it can happen is if + // there exists a long sequence of clusters none of which contain a + // block from this track. One way around this problem is for the + // caller to be smarter when he loads another cluster: don't call + // us back until you have a cluster that contains a block from this + // track. (Of course, that's not cheap either, since our caller + // would have to scan the each cluster as it's loaded, so that + // would just push back the problem.) + + pNextEntry = NULL; + return E_BUFFER_NOT_FULL; + } + + status = pCluster->GetFirst(pNextEntry); + + if (status < 0) // error + return status; + + if (pNextEntry == NULL) // empty cluster + continue; + + ++i; + + if (i >= 100) + break; + } + + // NOTE: if we get here, it means that we didn't find a block with + // a matching track number after lots of searching, so we give + // up trying. + + pNextEntry = GetEOS(); // so we can return a non-NULL value + return 1; +} + +bool Track::VetEntry(const BlockEntry* pBlockEntry) const { + assert(pBlockEntry); + const Block* const pBlock = pBlockEntry->GetBlock(); + assert(pBlock); + assert(pBlock->GetTrackNumber() == m_info.number); + if (!pBlock || pBlock->GetTrackNumber() != m_info.number) + return false; + + // This function is used during a seek to determine whether the + // frame is a valid seek target. This default function simply + // returns true, which means all frames are valid seek targets. + // It gets overridden by the VideoTrack class, because only video + // keyframes can be used as seek target. + + return true; +} + +long Track::Seek(long long time_ns, const BlockEntry*& pResult) const { + const long status = GetFirst(pResult); + + if (status < 0) // buffer underflow, etc + return status; + + assert(pResult); + + if (pResult->EOS()) + return 0; + + const Cluster* pCluster = pResult->GetCluster(); + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + + if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) + return 0; + + Cluster** const clusters = m_pSegment->m_clusters; + assert(clusters); + + const long count = m_pSegment->GetCount(); // loaded only, not preloaded + assert(count > 0); + + Cluster** const i = clusters + pCluster->GetIndex(); + assert(i); + assert(*i == pCluster); + assert(pCluster->GetTime() <= time_ns); + + Cluster** const j = clusters + count; + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) { + // INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + pCluster = *mid; + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + while (lo > i) { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this); + + if ((pResult != 0) && !pResult->EOS()) + return 0; + + // landed on empty cluster (no entries) + } + + pResult = GetEOS(); // weird + return 0; +} + +const ContentEncoding* Track::GetContentEncodingByIndex( + unsigned long idx) const { + const ptrdiff_t count = + content_encoding_entries_end_ - content_encoding_entries_; + assert(count >= 0); + + if (idx >= static_cast<unsigned long>(count)) + return NULL; + + return content_encoding_entries_[idx]; +} + +unsigned long Track::GetContentEncodingCount() const { + const ptrdiff_t count = + content_encoding_entries_end_ - content_encoding_entries_; + assert(count >= 0); + + return static_cast<unsigned long>(count); +} + +long Track::ParseContentEncodingsEntry(long long start, long long size) { + IMkvReader* const pReader = m_pSegment->m_pReader; + assert(pReader); + + long long pos = start; + const long long stop = start + size; + + // Count ContentEncoding elements. + int count = 0; + while (pos < stop) { + long long id, size; + const long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + // pos now designates start of element + if (id == libwebm::kMkvContentEncoding) + ++count; + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (count <= 0) + return -1; + + content_encoding_entries_ = new (std::nothrow) ContentEncoding*[count]; + if (!content_encoding_entries_) + return -1; + + content_encoding_entries_end_ = content_encoding_entries_; + + pos = start; + while (pos < stop) { + long long id, size; + long status = ParseElementHeader(pReader, pos, stop, id, size); + if (status < 0) // error + return status; + + // pos now designates start of element + if (id == libwebm::kMkvContentEncoding) { + ContentEncoding* const content_encoding = + new (std::nothrow) ContentEncoding(); + if (!content_encoding) + return -1; + + status = content_encoding->ParseContentEncodingEntry(pos, size, pReader); + if (status) { + delete content_encoding; + return status; + } + + *content_encoding_entries_end_++ = content_encoding; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + return 0; +} + +Track::EOSBlock::EOSBlock() : BlockEntry(NULL, LONG_MIN) {} + +BlockEntry::Kind Track::EOSBlock::GetKind() const { return kBlockEOS; } + +const Block* Track::EOSBlock::GetBlock() const { return NULL; } + +bool PrimaryChromaticity::Parse(IMkvReader* reader, long long read_pos, + long long value_size, bool is_x, + PrimaryChromaticity** chromaticity) { + if (!reader) + return false; + + if (!*chromaticity) + *chromaticity = new PrimaryChromaticity(); + + if (!*chromaticity) + return false; + + PrimaryChromaticity* pc = *chromaticity; + float* value = is_x ? &pc->x : &pc->y; + + double parser_value = 0; + const long long parse_status = + UnserializeFloat(reader, read_pos, value_size, parser_value); + + // Valid range is [0, 1]. Make sure the double is representable as a float + // before casting. + if (parse_status < 0 || parser_value < 0.0 || parser_value > 1.0 || + (parser_value > 0.0 && parser_value < FLT_MIN)) + return false; + + *value = static_cast<float>(parser_value); + + return true; +} + +bool MasteringMetadata::Parse(IMkvReader* reader, long long mm_start, + long long mm_size, MasteringMetadata** mm) { + if (!reader || *mm) + return false; + + std::unique_ptr<MasteringMetadata> mm_ptr(new MasteringMetadata()); + if (!mm_ptr.get()) + return false; + + const long long mm_end = mm_start + mm_size; + long long read_pos = mm_start; + + while (read_pos < mm_end) { + long long child_id = 0; + long long child_size = 0; + + const long long status = + ParseElementHeader(reader, read_pos, mm_end, child_id, child_size); + if (status < 0) + return false; + + if (child_id == libwebm::kMkvLuminanceMax) { + double value = 0; + const long long value_parse_status = + UnserializeFloat(reader, read_pos, child_size, value); + if (value < -FLT_MAX || value > FLT_MAX || + (value > 0.0 && value < FLT_MIN)) { + return false; + } + mm_ptr->luminance_max = static_cast<float>(value); + if (value_parse_status < 0 || mm_ptr->luminance_max < 0.0 || + mm_ptr->luminance_max > 9999.99) { + return false; + } + } else if (child_id == libwebm::kMkvLuminanceMin) { + double value = 0; + const long long value_parse_status = + UnserializeFloat(reader, read_pos, child_size, value); + if (value < -FLT_MAX || value > FLT_MAX || + (value > 0.0 && value < FLT_MIN)) { + return false; + } + mm_ptr->luminance_min = static_cast<float>(value); + if (value_parse_status < 0 || mm_ptr->luminance_min < 0.0 || + mm_ptr->luminance_min > 999.9999) { + return false; + } + } else { + bool is_x = false; + PrimaryChromaticity** chromaticity; + switch (child_id) { + case libwebm::kMkvPrimaryRChromaticityX: + case libwebm::kMkvPrimaryRChromaticityY: + is_x = child_id == libwebm::kMkvPrimaryRChromaticityX; + chromaticity = &mm_ptr->r; + break; + case libwebm::kMkvPrimaryGChromaticityX: + case libwebm::kMkvPrimaryGChromaticityY: + is_x = child_id == libwebm::kMkvPrimaryGChromaticityX; + chromaticity = &mm_ptr->g; + break; + case libwebm::kMkvPrimaryBChromaticityX: + case libwebm::kMkvPrimaryBChromaticityY: + is_x = child_id == libwebm::kMkvPrimaryBChromaticityX; + chromaticity = &mm_ptr->b; + break; + case libwebm::kMkvWhitePointChromaticityX: + case libwebm::kMkvWhitePointChromaticityY: + is_x = child_id == libwebm::kMkvWhitePointChromaticityX; + chromaticity = &mm_ptr->white_point; + break; + default: + return false; + } + const bool value_parse_status = PrimaryChromaticity::Parse( + reader, read_pos, child_size, is_x, chromaticity); + if (!value_parse_status) + return false; + } + + read_pos += child_size; + if (read_pos > mm_end) + return false; + } + + *mm = mm_ptr.release(); + return true; +} + +bool Colour::Parse(IMkvReader* reader, long long colour_start, + long long colour_size, Colour** colour) { + if (!reader || *colour) + return false; + + std::unique_ptr<Colour> colour_ptr(new Colour()); + if (!colour_ptr.get()) + return false; + + const long long colour_end = colour_start + colour_size; + long long read_pos = colour_start; + + while (read_pos < colour_end) { + long long child_id = 0; + long long child_size = 0; + + const long status = + ParseElementHeader(reader, read_pos, colour_end, child_id, child_size); + if (status < 0) + return false; + + if (child_id == libwebm::kMkvMatrixCoefficients) { + colour_ptr->matrix_coefficients = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->matrix_coefficients < 0) + return false; + } else if (child_id == libwebm::kMkvBitsPerChannel) { + colour_ptr->bits_per_channel = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->bits_per_channel < 0) + return false; + } else if (child_id == libwebm::kMkvChromaSubsamplingHorz) { + colour_ptr->chroma_subsampling_horz = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->chroma_subsampling_horz < 0) + return false; + } else if (child_id == libwebm::kMkvChromaSubsamplingVert) { + colour_ptr->chroma_subsampling_vert = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->chroma_subsampling_vert < 0) + return false; + } else if (child_id == libwebm::kMkvCbSubsamplingHorz) { + colour_ptr->cb_subsampling_horz = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->cb_subsampling_horz < 0) + return false; + } else if (child_id == libwebm::kMkvCbSubsamplingVert) { + colour_ptr->cb_subsampling_vert = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->cb_subsampling_vert < 0) + return false; + } else if (child_id == libwebm::kMkvChromaSitingHorz) { + colour_ptr->chroma_siting_horz = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->chroma_siting_horz < 0) + return false; + } else if (child_id == libwebm::kMkvChromaSitingVert) { + colour_ptr->chroma_siting_vert = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->chroma_siting_vert < 0) + return false; + } else if (child_id == libwebm::kMkvRange) { + colour_ptr->range = UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->range < 0) + return false; + } else if (child_id == libwebm::kMkvTransferCharacteristics) { + colour_ptr->transfer_characteristics = + UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->transfer_characteristics < 0) + return false; + } else if (child_id == libwebm::kMkvPrimaries) { + colour_ptr->primaries = UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->primaries < 0) + return false; + } else if (child_id == libwebm::kMkvMaxCLL) { + colour_ptr->max_cll = UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->max_cll < 0) + return false; + } else if (child_id == libwebm::kMkvMaxFALL) { + colour_ptr->max_fall = UnserializeUInt(reader, read_pos, child_size); + if (colour_ptr->max_fall < 0) + return false; + } else if (child_id == libwebm::kMkvMasteringMetadata) { + if (!MasteringMetadata::Parse(reader, read_pos, child_size, + &colour_ptr->mastering_metadata)) + return false; + } else { + return false; + } + + read_pos += child_size; + if (read_pos > colour_end) + return false; + } + *colour = colour_ptr.release(); + return true; +} + +bool Projection::Parse(IMkvReader* reader, long long start, long long size, + Projection** projection) { + if (!reader || *projection) + return false; + + std::unique_ptr<Projection> projection_ptr(new Projection()); + if (!projection_ptr.get()) + return false; + + const long long end = start + size; + long long read_pos = start; + + while (read_pos < end) { + long long child_id = 0; + long long child_size = 0; + + const long long status = + ParseElementHeader(reader, read_pos, end, child_id, child_size); + if (status < 0) + return false; + + if (child_id == libwebm::kMkvProjectionType) { + long long projection_type = kTypeNotPresent; + projection_type = UnserializeUInt(reader, read_pos, child_size); + if (projection_type < 0) + return false; + + projection_ptr->type = static_cast<ProjectionType>(projection_type); + } else if (child_id == libwebm::kMkvProjectionPrivate) { + if (projection_ptr->private_data != NULL) + return false; + unsigned char* data = SafeArrayAlloc<unsigned char>(1, child_size); + + if (data == NULL) + return false; + + const int status = + reader->Read(read_pos, static_cast<long>(child_size), data); + + if (status) { + delete[] data; + return false; + } + + projection_ptr->private_data = data; + projection_ptr->private_data_length = static_cast<size_t>(child_size); + } else { + double value = 0; + const long long value_parse_status = + UnserializeFloat(reader, read_pos, child_size, value); + // Make sure value is representable as a float before casting. + if (value_parse_status < 0 || value < -FLT_MAX || value > FLT_MAX || + (value > 0.0 && value < FLT_MIN)) { + return false; + } + + switch (child_id) { + case libwebm::kMkvProjectionPoseYaw: + projection_ptr->pose_yaw = static_cast<float>(value); + break; + case libwebm::kMkvProjectionPosePitch: + projection_ptr->pose_pitch = static_cast<float>(value); + break; + case libwebm::kMkvProjectionPoseRoll: + projection_ptr->pose_roll = static_cast<float>(value); + break; + default: + return false; + } + } + + read_pos += child_size; + if (read_pos > end) + return false; + } + + *projection = projection_ptr.release(); + return true; +} + +VideoTrack::VideoTrack(Segment* pSegment, long long element_start, + long long element_size) + : Track(pSegment, element_start, element_size), + m_colour_space(NULL), + m_colour(NULL), + m_projection(NULL) {} + +VideoTrack::~VideoTrack() { + delete[] m_colour_space; + delete m_colour; + delete m_projection; +} + +long VideoTrack::Parse(Segment* pSegment, const Info& info, + long long element_start, long long element_size, + VideoTrack*& pResult) { + if (pResult) + return -1; + + if (info.type != Track::kVideo) + return -1; + + long long width = 0; + long long height = 0; + long long display_width = 0; + long long display_height = 0; + long long display_unit = 0; + long long stereo_mode = 0; + + double rate = 0.0; + std::unique_ptr<char[]> colour_space_ptr; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + std::unique_ptr<Colour> colour_ptr; + std::unique_ptr<Projection> projection_ptr; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == libwebm::kMkvPixelWidth) { + width = UnserializeUInt(pReader, pos, size); + + if (width <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvPixelHeight) { + height = UnserializeUInt(pReader, pos, size); + + if (height <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvDisplayWidth) { + display_width = UnserializeUInt(pReader, pos, size); + + if (display_width <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvDisplayHeight) { + display_height = UnserializeUInt(pReader, pos, size); + + if (display_height <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvDisplayUnit) { + display_unit = UnserializeUInt(pReader, pos, size); + + if (display_unit < 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvStereoMode) { + stereo_mode = UnserializeUInt(pReader, pos, size); + + if (stereo_mode < 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvFrameRate) { + const long status = UnserializeFloat(pReader, pos, size, rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvColour) { + Colour* colour = NULL; + if (!Colour::Parse(pReader, pos, size, &colour)) { + return E_FILE_FORMAT_INVALID; + } else { + colour_ptr.reset(colour); + } + } else if (id == libwebm::kMkvProjection) { + Projection* projection = NULL; + if (!Projection::Parse(pReader, pos, size, &projection)) { + return E_FILE_FORMAT_INVALID; + } else { + projection_ptr.reset(projection); + } + } else if (id == libwebm::kMkvColourSpace) { + char* colour_space = NULL; + const long status = UnserializeString(pReader, pos, size, colour_space); + if (status < 0) + return status; + colour_space_ptr.reset(colour_space); + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + VideoTrack* const pTrack = + new (std::nothrow) VideoTrack(pSegment, element_start, element_size); + + if (pTrack == NULL) + return -1; // generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) { // error + delete pTrack; + return status; + } + + pTrack->m_width = width; + pTrack->m_height = height; + pTrack->m_display_width = display_width; + pTrack->m_display_height = display_height; + pTrack->m_display_unit = display_unit; + pTrack->m_stereo_mode = stereo_mode; + pTrack->m_rate = rate; + pTrack->m_colour = colour_ptr.release(); + pTrack->m_colour_space = colour_space_ptr.release(); + pTrack->m_projection = projection_ptr.release(); + + pResult = pTrack; + return 0; // success +} + +bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const { + return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey(); +} + +long VideoTrack::Seek(long long time_ns, const BlockEntry*& pResult) const { + const long status = GetFirst(pResult); + + if (status < 0) // buffer underflow, etc + return status; + + assert(pResult); + + if (pResult->EOS()) + return 0; + + const Cluster* pCluster = pResult->GetCluster(); + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + + if (time_ns <= pResult->GetBlock()->GetTime(pCluster)) + return 0; + + Cluster** const clusters = m_pSegment->m_clusters; + assert(clusters); + + const long count = m_pSegment->GetCount(); // loaded only, not pre-loaded + assert(count > 0); + + Cluster** const i = clusters + pCluster->GetIndex(); + assert(i); + assert(*i == pCluster); + assert(pCluster->GetTime() <= time_ns); + + Cluster** const j = clusters + count; + + Cluster** lo = i; + Cluster** hi = j; + + while (lo < hi) { + // INVARIANT: + //[i, lo) <= time_ns + //[lo, hi) ? + //[hi, j) > time_ns + + Cluster** const mid = lo + (hi - lo) / 2; + assert(mid < hi); + + pCluster = *mid; + assert(pCluster); + assert(pCluster->GetIndex() >= 0); + assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters)); + + const long long t = pCluster->GetTime(); + + if (t <= time_ns) + lo = mid + 1; + else + hi = mid; + + assert(lo <= hi); + } + + assert(lo == hi); + assert(lo > i); + assert(lo <= j); + + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this, time_ns); + + if ((pResult != 0) && !pResult->EOS()) // found a keyframe + return 0; + + while (lo != i) { + pCluster = *--lo; + assert(pCluster); + assert(pCluster->GetTime() <= time_ns); + + pResult = pCluster->GetEntry(this, time_ns); + + if ((pResult != 0) && !pResult->EOS()) + return 0; + } + + // weird: we're on the first cluster, but no keyframe found + // should never happen but we must return something anyway + + pResult = GetEOS(); + return 0; +} + +Colour* VideoTrack::GetColour() const { return m_colour; } + +Projection* VideoTrack::GetProjection() const { return m_projection; } + +long long VideoTrack::GetWidth() const { return m_width; } + +long long VideoTrack::GetHeight() const { return m_height; } + +long long VideoTrack::GetDisplayWidth() const { + return m_display_width > 0 ? m_display_width : GetWidth(); +} + +long long VideoTrack::GetDisplayHeight() const { + return m_display_height > 0 ? m_display_height : GetHeight(); +} + +long long VideoTrack::GetDisplayUnit() const { return m_display_unit; } + +long long VideoTrack::GetStereoMode() const { return m_stereo_mode; } + +double VideoTrack::GetFrameRate() const { return m_rate; } + +AudioTrack::AudioTrack(Segment* pSegment, long long element_start, + long long element_size) + : Track(pSegment, element_start, element_size) {} + +long AudioTrack::Parse(Segment* pSegment, const Info& info, + long long element_start, long long element_size, + AudioTrack*& pResult) { + if (pResult) + return -1; + + if (info.type != Track::kAudio) + return -1; + + IMkvReader* const pReader = pSegment->m_pReader; + + const Settings& s = info.settings; + assert(s.start >= 0); + assert(s.size >= 0); + + long long pos = s.start; + assert(pos >= 0); + + const long long stop = pos + s.size; + + double rate = 8000.0; // MKV default + long long channels = 1; + long long bit_depth = 0; + + while (pos < stop) { + long long id, size; + + long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (id == libwebm::kMkvSamplingFrequency) { + status = UnserializeFloat(pReader, pos, size, rate); + + if (status < 0) + return status; + + if (rate <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvChannels) { + channels = UnserializeUInt(pReader, pos, size); + + if (channels <= 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvBitDepth) { + bit_depth = UnserializeUInt(pReader, pos, size); + + if (bit_depth <= 0) + return E_FILE_FORMAT_INVALID; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + AudioTrack* const pTrack = + new (std::nothrow) AudioTrack(pSegment, element_start, element_size); + + if (pTrack == NULL) + return -1; // generic error + + const int status = info.Copy(pTrack->m_info); + + if (status) { + delete pTrack; + return status; + } + + pTrack->m_rate = rate; + pTrack->m_channels = channels; + pTrack->m_bitDepth = bit_depth; + + pResult = pTrack; + return 0; // success +} + +double AudioTrack::GetSamplingRate() const { return m_rate; } + +long long AudioTrack::GetChannels() const { return m_channels; } + +long long AudioTrack::GetBitDepth() const { return m_bitDepth; } + +Tracks::Tracks(Segment* pSegment, long long start, long long size_, + long long element_start, long long element_size) + : m_pSegment(pSegment), + m_start(start), + m_size(size_), + m_element_start(element_start), + m_element_size(element_size), + m_trackEntries(NULL), + m_trackEntriesEnd(NULL) {} + +long Tracks::Parse() { + assert(m_trackEntries == NULL); + assert(m_trackEntriesEnd == NULL); + + const long long stop = m_start + m_size; + IMkvReader* const pReader = m_pSegment->m_pReader; + + int count = 0; + long long pos = m_start; + + while (pos < stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, stop, id, size); + + if (status < 0) // error + return status; + + if (size == 0) // weird + continue; + + if (id == libwebm::kMkvTrackEntry) + ++count; + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + if (count <= 0) + return 0; // success + + m_trackEntries = new (std::nothrow) Track*[count]; + + if (m_trackEntries == NULL) + return -1; + + m_trackEntriesEnd = m_trackEntries; + + pos = m_start; + + while (pos < stop) { + const long long element_start = pos; + + long long id, payload_size; + + const long status = + ParseElementHeader(pReader, pos, stop, id, payload_size); + + if (status < 0) // error + return status; + + if (payload_size == 0) // weird + continue; + + const long long payload_stop = pos + payload_size; + assert(payload_stop <= stop); // checked in ParseElement + + const long long element_size = payload_stop - element_start; + + if (id == libwebm::kMkvTrackEntry) { + Track*& pTrack = *m_trackEntriesEnd; + pTrack = NULL; + + const long status = ParseTrackEntry(pos, payload_size, element_start, + element_size, pTrack); + if (status) + return status; + + if (pTrack) + ++m_trackEntriesEnd; + } + + pos = payload_stop; + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + return 0; // success +} + +unsigned long Tracks::GetTracksCount() const { + const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries; + assert(result >= 0); + + return static_cast<unsigned long>(result); +} + +long Tracks::ParseTrackEntry(long long track_start, long long track_size, + long long element_start, long long element_size, + Track*& pResult) const { + if (pResult) + return -1; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = track_start; + const long long track_stop = track_start + track_size; + + Track::Info info; + + info.type = 0; + info.number = 0; + info.uid = 0; + info.defaultDuration = 0; + + Track::Settings v; + v.start = -1; + v.size = -1; + + Track::Settings a; + a.start = -1; + a.size = -1; + + Track::Settings e; // content_encodings_settings; + e.start = -1; + e.size = -1; + + long long lacing = 1; // default is true + + while (pos < track_stop) { + long long id, size; + + const long status = ParseElementHeader(pReader, pos, track_stop, id, size); + + if (status < 0) // error + return status; + + if (size < 0) + return E_FILE_FORMAT_INVALID; + + const long long start = pos; + + if (id == libwebm::kMkvVideo) { + v.start = start; + v.size = size; + } else if (id == libwebm::kMkvAudio) { + a.start = start; + a.size = size; + } else if (id == libwebm::kMkvContentEncodings) { + e.start = start; + e.size = size; + } else if (id == libwebm::kMkvTrackUID) { + if (size > 8) + return E_FILE_FORMAT_INVALID; + + info.uid = 0; + + long long pos_ = start; + const long long pos_end = start + size; + + while (pos_ != pos_end) { + unsigned char b; + + const int status = pReader->Read(pos_, 1, &b); + + if (status) + return status; + + info.uid <<= 8; + info.uid |= b; + + ++pos_; + } + } else if (id == libwebm::kMkvTrackNumber) { + const long long num = UnserializeUInt(pReader, pos, size); + + if ((num <= 0) || (num > 127)) + return E_FILE_FORMAT_INVALID; + + info.number = static_cast<long>(num); + } else if (id == libwebm::kMkvTrackType) { + const long long type = UnserializeUInt(pReader, pos, size); + + if ((type <= 0) || (type > 254)) + return E_FILE_FORMAT_INVALID; + + info.type = static_cast<long>(type); + } else if (id == libwebm::kMkvName) { + const long status = + UnserializeString(pReader, pos, size, info.nameAsUTF8); + + if (status) + return status; + } else if (id == libwebm::kMkvLanguage) { + const long status = UnserializeString(pReader, pos, size, info.language); + + if (status) + return status; + } else if (id == libwebm::kMkvDefaultDuration) { + const long long duration = UnserializeUInt(pReader, pos, size); + + if (duration < 0) + return E_FILE_FORMAT_INVALID; + + info.defaultDuration = static_cast<unsigned long long>(duration); + } else if (id == libwebm::kMkvCodecID) { + const long status = UnserializeString(pReader, pos, size, info.codecId); + + if (status) + return status; + } else if (id == libwebm::kMkvFlagLacing) { + lacing = UnserializeUInt(pReader, pos, size); + + if ((lacing < 0) || (lacing > 1)) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvCodecPrivate) { + delete[] info.codecPrivate; + info.codecPrivate = NULL; + info.codecPrivateSize = 0; + + const size_t buflen = static_cast<size_t>(size); + + if (buflen) { + unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen); + + if (buf == NULL) + return -1; + + const int status = pReader->Read(pos, static_cast<long>(buflen), buf); + + if (status) { + delete[] buf; + return status; + } + + info.codecPrivate = buf; + info.codecPrivateSize = buflen; + } + } else if (id == libwebm::kMkvCodecName) { + const long status = + UnserializeString(pReader, pos, size, info.codecNameAsUTF8); + + if (status) + return status; + } else if (id == libwebm::kMkvCodecDelay) { + info.codecDelay = UnserializeUInt(pReader, pos, size); + } else if (id == libwebm::kMkvSeekPreRoll) { + info.seekPreRoll = UnserializeUInt(pReader, pos, size); + } + + pos += size; // consume payload + if (pos > track_stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != track_stop) + return E_FILE_FORMAT_INVALID; + + if (info.number <= 0) // not specified + return E_FILE_FORMAT_INVALID; + + if (GetTrackByNumber(info.number)) + return E_FILE_FORMAT_INVALID; + + if (info.type <= 0) // not specified + return E_FILE_FORMAT_INVALID; + + info.lacing = (lacing > 0) ? true : false; + + if (info.type == Track::kVideo) { + if (v.start < 0) + return E_FILE_FORMAT_INVALID; + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = v; + + VideoTrack* pTrack = NULL; + + const long status = VideoTrack::Parse(m_pSegment, info, element_start, + element_size, pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } else if (info.type == Track::kAudio) { + if (a.start < 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings = a; + + AudioTrack* pTrack = NULL; + + const long status = AudioTrack::Parse(m_pSegment, info, element_start, + element_size, pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + + if (e.start >= 0) + pResult->ParseContentEncodingsEntry(e.start, e.size); + } else { + // neither video nor audio - probably metadata or subtitles + + if (a.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (v.start >= 0) + return E_FILE_FORMAT_INVALID; + + if (info.type == Track::kMetadata && e.start >= 0) + return E_FILE_FORMAT_INVALID; + + info.settings.start = -1; + info.settings.size = 0; + + Track* pTrack = NULL; + + const long status = + Track::Create(m_pSegment, info, element_start, element_size, pTrack); + + if (status) + return status; + + pResult = pTrack; + assert(pResult); + } + + return 0; // success +} + +Tracks::~Tracks() { + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) { + Track* const pTrack = *i++; + delete pTrack; + } + + delete[] m_trackEntries; +} + +const Track* Tracks::GetTrackByNumber(long tn) const { + if (tn < 0) + return NULL; + + Track** i = m_trackEntries; + Track** const j = m_trackEntriesEnd; + + while (i != j) { + Track* const pTrack = *i++; + + if (pTrack == NULL) + continue; + + if (tn == pTrack->GetNumber()) + return pTrack; + } + + return NULL; // not found +} + +const Track* Tracks::GetTrackByIndex(unsigned long idx) const { + const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries; + + if (idx >= static_cast<unsigned long>(count)) + return NULL; + + return m_trackEntries[idx]; +} + +long Cluster::Load(long long& pos, long& len) const { + if (m_pSegment == NULL) + return E_PARSE_FAILED; + + if (m_timecode >= 0) // at least partially loaded + return 0; + + if (m_pos != m_element_start || m_element_size >= 0) + return E_PARSE_FAILED; + + IMkvReader* const pReader = m_pSegment->m_pReader; + long long total, avail; + const int status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + if (total >= 0 && (avail > total || m_pos > total)) + return E_FILE_FORMAT_INVALID; + + pos = m_pos; + + long long cluster_size = -1; + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error or underflow + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id_ = ReadID(pReader, pos, len); + + if (id_ < 0) // error + return static_cast<long>(id_); + + if (id_ != libwebm::kMkvCluster) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume id + + // read cluster size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(cluster_size); + + if (size == 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume length of size of element + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size != unknown_size) + cluster_size = size; + + // pos points to start of payload + long long timecode = -1; + long long new_pos = -1; + bool bBlock = false; + + long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size; + + for (;;) { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + // Parse ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadID(pReader, pos, len); + + if (id < 0) // error + return static_cast<long>(id); + + if (id == 0) + return E_FILE_FORMAT_INVALID; + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if (id == libwebm::kMkvCluster) + break; + + if (id == libwebm::kMkvCues) + break; + + pos += len; // consume ID field + + // Parse Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + // pos now points to start of payload + + if (size == 0) + continue; + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == libwebm::kMkvTimecode) { + len = static_cast<long>(size); + + if ((pos + size) > avail) + return E_BUFFER_NOT_FULL; + + timecode = UnserializeUInt(pReader, pos, size); + + if (timecode < 0) // error (or underflow) + return static_cast<long>(timecode); + + new_pos = pos + size; + + if (bBlock) + break; + } else if (id == libwebm::kMkvBlockGroup) { + bBlock = true; + break; + } else if (id == libwebm::kMkvSimpleBlock) { + bBlock = true; + break; + } + + pos += size; // consume payload + if (cluster_stop >= 0 && pos > cluster_stop) + return E_FILE_FORMAT_INVALID; + } + + if (cluster_stop >= 0 && pos > cluster_stop) + return E_FILE_FORMAT_INVALID; + + if (timecode < 0) // no timecode found + return E_FILE_FORMAT_INVALID; + + if (!bBlock) + return E_FILE_FORMAT_INVALID; + + m_pos = new_pos; // designates position just beyond timecode payload + m_timecode = timecode; // m_timecode >= 0 means we're partially loaded + + if (cluster_size >= 0) + m_element_size = cluster_stop - m_element_start; + + return 0; +} + +long Cluster::Parse(long long& pos, long& len) const { + long status = Load(pos, len); + + if (status < 0) + return status; + + if (m_pos < m_element_start || m_timecode < 0) + return E_PARSE_FAILED; + + const long long cluster_stop = + (m_element_size < 0) ? -1 : m_element_start + m_element_size; + + if ((cluster_stop >= 0) && (m_pos >= cluster_stop)) + return 1; // nothing else to do + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + if (total >= 0 && avail > total) + return E_FILE_FORMAT_INVALID; + + pos = m_pos; + + for (;;) { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + break; + + if ((total >= 0) && (pos >= total)) { + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + // Parse ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadID(pReader, pos, len); + + if (id < 0) + return E_FILE_FORMAT_INVALID; + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if ((id == libwebm::kMkvCluster) || (id == libwebm::kMkvCues)) { + if (m_element_size < 0) + m_element_size = pos - m_element_start; + + break; + } + + pos += len; // consume ID field + + // Parse Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume size field + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + // pos now points to start of payload + + if (size == 0) + continue; + + // const long long block_start = pos; + const long long block_stop = pos + size; + + if (cluster_stop >= 0) { + if (block_stop > cluster_stop) { + if (id == libwebm::kMkvBlockGroup || id == libwebm::kMkvSimpleBlock) { + return E_FILE_FORMAT_INVALID; + } + + pos = cluster_stop; + break; + } + } else if ((total >= 0) && (block_stop > total)) { + m_element_size = total - m_element_start; + pos = total; + break; + } else if (block_stop > avail) { + len = static_cast<long>(size); + return E_BUFFER_NOT_FULL; + } + + Cluster* const this_ = const_cast<Cluster*>(this); + + if (id == libwebm::kMkvBlockGroup) + return this_->ParseBlockGroup(size, pos, len); + + if (id == libwebm::kMkvSimpleBlock) + return this_->ParseSimpleBlock(size, pos, len); + + pos += size; // consume payload + if (cluster_stop >= 0 && pos > cluster_stop) + return E_FILE_FORMAT_INVALID; + } + + if (m_element_size < 1) + return E_FILE_FORMAT_INVALID; + + m_pos = pos; + if (cluster_stop >= 0 && m_pos > cluster_stop) + return E_FILE_FORMAT_INVALID; + + if (m_entries_count > 0) { + const long idx = m_entries_count - 1; + + const BlockEntry* const pLast = m_entries[idx]; + if (pLast == NULL) + return E_PARSE_FAILED; + + const Block* const pBlock = pLast->GetBlock(); + if (pBlock == NULL) + return E_PARSE_FAILED; + + const long long start = pBlock->m_start; + + if ((total >= 0) && (start > total)) + return E_PARSE_FAILED; // defend against trucated stream + + const long long size = pBlock->m_size; + + const long long stop = start + size; + if (cluster_stop >= 0 && stop > cluster_stop) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && (stop > total)) + return E_PARSE_FAILED; // defend against trucated stream + } + + return 1; // no more entries +} + +long Cluster::ParseSimpleBlock(long long block_size, long long& pos, + long& len) { + const long long block_start = pos; + const long long block_stop = pos + block_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + // parse track number + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long track = ReadUInt(pReader, pos, len); + + if (track < 0) // error + return static_cast<long>(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; // consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) { // error or underflow + len = 1; + return status; + } + + ++pos; // consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) { + len = static_cast<long>(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + status = CreateBlock(libwebm::kMkvSimpleBlock, block_start, block_size, + 0); // DiscardPadding + + if (status != 0) + return status; + + m_pos = block_stop; + + return 0; // success +} + +long Cluster::ParseBlockGroup(long long payload_size, long long& pos, + long& len) { + const long long payload_start = pos; + const long long payload_stop = pos + payload_size; + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + if ((total >= 0) && (payload_stop > total)) + return E_FILE_FORMAT_INVALID; + + if (payload_stop > avail) { + len = static_cast<long>(payload_size); + return E_BUFFER_NOT_FULL; + } + + long long discard_padding = 0; + + while (pos < payload_stop) { + // parse sub-block element ID + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > payload_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadID(pReader, pos, len); + + if (id < 0) // error + return static_cast<long>(id); + + if (id == 0) // not a valid ID + return E_FILE_FORMAT_INVALID; + + pos += len; // consume ID field + + // Parse Size + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > payload_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + pos += len; // consume size field + + // pos now points to start of sub-block group payload + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; + + if (id == libwebm::kMkvDiscardPadding) { + status = UnserializeInt(pReader, pos, size, discard_padding); + + if (status < 0) // error + return status; + } + + if (id != libwebm::kMkvBlock) { + pos += size; // consume sub-part of block group + + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + + continue; + } + + const long long block_stop = pos + size; + + if (block_stop > payload_stop) + return E_FILE_FORMAT_INVALID; + + // parse track number + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((pos + len) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long track = ReadUInt(pReader, pos, len); + + if (track < 0) // error + return static_cast<long>(track); + + if (track == 0) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume track number + + if ((pos + 2) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 2) > avail) { + len = 2; + return E_BUFFER_NOT_FULL; + } + + pos += 2; // consume timecode + + if ((pos + 1) > block_stop) + return E_FILE_FORMAT_INVALID; + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + unsigned char flags; + + status = pReader->Read(pos, 1, &flags); + + if (status < 0) { // error or underflow + len = 1; + return status; + } + + ++pos; // consume flags byte + assert(pos <= avail); + + if (pos >= block_stop) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(flags & 0x06) >> 1; + + if ((lacing != 0) && (block_stop > avail)) { + len = static_cast<long>(block_stop - pos); + return E_BUFFER_NOT_FULL; + } + + pos = block_stop; // consume block-part of block group + if (pos > payload_stop) + return E_FILE_FORMAT_INVALID; + } + + if (pos != payload_stop) + return E_FILE_FORMAT_INVALID; + + status = CreateBlock(libwebm::kMkvBlockGroup, payload_start, payload_size, + discard_padding); + if (status != 0) + return status; + + m_pos = payload_stop; + + return 0; // success +} + +long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const { + assert(m_pos >= m_element_start); + + pEntry = NULL; + + if (index < 0) + return -1; // generic error + + if (m_entries_count < 0) + return E_BUFFER_NOT_FULL; + + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (index < m_entries_count) { + pEntry = m_entries[index]; + assert(pEntry); + + return 1; // found entry + } + + if (m_element_size < 0) // we don't know cluster end yet + return E_BUFFER_NOT_FULL; // underflow + + const long long element_stop = m_element_start + m_element_size; + + if (m_pos >= element_stop) + return 0; // nothing left to parse + + return E_BUFFER_NOT_FULL; // underflow, since more remains to be parsed +} + +Cluster* Cluster::Create(Segment* pSegment, long idx, long long off) { + if (!pSegment || off < 0) + return NULL; + + const long long element_start = pSegment->m_start + off; + + Cluster* const pCluster = + new (std::nothrow) Cluster(pSegment, idx, element_start); + + return pCluster; +} + +Cluster::Cluster() + : m_pSegment(NULL), + m_element_start(0), + m_index(0), + m_pos(0), + m_element_size(0), + m_timecode(0), + m_entries(NULL), + m_entries_size(0), + m_entries_count(0) // means "no entries" +{} + +Cluster::Cluster(Segment* pSegment, long idx, long long element_start + /* long long element_size */) + : m_pSegment(pSegment), + m_element_start(element_start), + m_index(idx), + m_pos(element_start), + m_element_size(-1 /* element_size */), + m_timecode(-1), + m_entries(NULL), + m_entries_size(0), + m_entries_count(-1) // means "has not been parsed yet" +{} + +Cluster::~Cluster() { + if (m_entries_count <= 0) { + delete[] m_entries; + return; + } + + BlockEntry** i = m_entries; + BlockEntry** const j = m_entries + m_entries_count; + + while (i != j) { + BlockEntry* p = *i++; + assert(p); + + delete p; + } + + delete[] m_entries; +} + +bool Cluster::EOS() const { return (m_pSegment == NULL); } + +long Cluster::GetIndex() const { return m_index; } + +long long Cluster::GetPosition() const { + const long long pos = m_element_start - m_pSegment->m_start; + assert(pos >= 0); + + return pos; +} + +long long Cluster::GetElementSize() const { return m_element_size; } + +long Cluster::HasBlockEntries( + const Segment* pSegment, + long long off, // relative to start of segment payload + long long& pos, long& len) { + assert(pSegment); + assert(off >= 0); // relative to segment + + IMkvReader* const pReader = pSegment->m_pReader; + + long long total, avail; + + long status = pReader->Length(&total, &avail); + + if (status < 0) // error + return status; + + assert((total < 0) || (avail <= total)); + + pos = pSegment->m_start + off; // absolute + + if ((total >= 0) && (pos >= total)) + return 0; // we don't even have a complete cluster + + const long long segment_stop = + (pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size; + + long long cluster_stop = -1; // interpreted later to mean "unknown size" + + { + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // need more data + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + len) > total)) + return 0; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadID(pReader, pos, len); + + if (id < 0) // error + return static_cast<long>(id); + + if (id != libwebm::kMkvCluster) + return E_PARSE_FAILED; + + pos += len; // consume Cluster ID field + + // read size field + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // weird + return E_BUFFER_NOT_FULL; + + if ((segment_stop >= 0) && ((pos + len) > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && ((pos + len) > total)) + return 0; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + if (size == 0) + return 0; // cluster does not have entries + + pos += len; // consume size field + + // pos now points to start of payload + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size != unknown_size) { + cluster_stop = pos + size; + assert(cluster_stop >= 0); + + if ((segment_stop >= 0) && (cluster_stop > segment_stop)) + return E_FILE_FORMAT_INVALID; + + if ((total >= 0) && (cluster_stop > total)) + // return E_FILE_FORMAT_INVALID; //too conservative + return 0; // cluster does not have any entries + } + } + + for (;;) { + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + return 0; // no entries detected + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + long long result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // need more data + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long id = ReadID(pReader, pos, len); + + if (id < 0) // error + return static_cast<long>(id); + + // This is the distinguished set of ID's we use to determine + // that we have exhausted the sub-element's inside the cluster + // whose ID we parsed earlier. + + if (id == libwebm::kMkvCluster) + return 0; // no entries found + + if (id == libwebm::kMkvCues) + return 0; // no entries found + + pos += len; // consume id field + + if ((cluster_stop >= 0) && (pos >= cluster_stop)) + return E_FILE_FORMAT_INVALID; + + // read size field + + if ((pos + 1) > avail) { + len = 1; + return E_BUFFER_NOT_FULL; + } + + result = GetUIntLength(pReader, pos, len); + + if (result < 0) // error + return static_cast<long>(result); + + if (result > 0) // underflow + return E_BUFFER_NOT_FULL; + + if ((cluster_stop >= 0) && ((pos + len) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > avail) + return E_BUFFER_NOT_FULL; + + const long long size = ReadUInt(pReader, pos, len); + + if (size < 0) // error + return static_cast<long>(size); + + pos += len; // consume size field + + // pos now points to start of payload + + if ((cluster_stop >= 0) && (pos > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (size == 0) // weird + continue; + + const long long unknown_size = (1LL << (7 * len)) - 1; + + if (size == unknown_size) + return E_FILE_FORMAT_INVALID; // not supported inside cluster + + if ((cluster_stop >= 0) && ((pos + size) > cluster_stop)) + return E_FILE_FORMAT_INVALID; + + if (id == libwebm::kMkvBlockGroup) + return 1; // have at least one entry + + if (id == libwebm::kMkvSimpleBlock) + return 1; // have at least one entry + + pos += size; // consume payload + if (cluster_stop >= 0 && pos > cluster_stop) + return E_FILE_FORMAT_INVALID; + } +} + +long long Cluster::GetTimeCode() const { + long long pos; + long len; + + const long status = Load(pos, len); + + if (status < 0) // error + return status; + + return m_timecode; +} + +long long Cluster::GetTime() const { + const long long tc = GetTimeCode(); + + if (tc < 0) + return tc; + + const SegmentInfo* const pInfo = m_pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + const long long t = m_timecode * scale; + + return t; +} + +long long Cluster::GetFirstTime() const { + const BlockEntry* pEntry; + + const long status = GetFirst(pEntry); + + if (status < 0) // error + return status; + + if (pEntry == NULL) // empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + +long long Cluster::GetLastTime() const { + const BlockEntry* pEntry; + + const long status = GetLast(pEntry); + + if (status < 0) // error + return status; + + if (pEntry == NULL) // empty cluster + return GetTime(); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + return pBlock->GetTime(this); +} + +long Cluster::CreateBlock(long long id, + long long pos, // absolute pos of payload + long long size, long long discard_padding) { + if (id != libwebm::kMkvBlockGroup && id != libwebm::kMkvSimpleBlock) + return E_PARSE_FAILED; + + if (m_entries_count < 0) { // haven't parsed anything yet + assert(m_entries == NULL); + assert(m_entries_size == 0); + + m_entries_size = 1024; + m_entries = new (std::nothrow) BlockEntry*[m_entries_size]; + if (m_entries == NULL) + return -1; + + m_entries_count = 0; + } else { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count <= m_entries_size); + + if (m_entries_count >= m_entries_size) { + const long entries_size = 2 * m_entries_size; + + BlockEntry** const entries = new (std::nothrow) BlockEntry*[entries_size]; + if (entries == NULL) + return -1; + + BlockEntry** src = m_entries; + BlockEntry** const src_end = src + m_entries_count; + + BlockEntry** dst = entries; + + while (src != src_end) + *dst++ = *src++; + + delete[] m_entries; + + m_entries = entries; + m_entries_size = entries_size; + } + } + + if (id == libwebm::kMkvBlockGroup) + return CreateBlockGroup(pos, size, discard_padding); + else + return CreateSimpleBlock(pos, size); +} + +long Cluster::CreateBlockGroup(long long start_offset, long long size, + long long discard_padding) { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count >= 0); + assert(m_entries_count < m_entries_size); + + IMkvReader* const pReader = m_pSegment->m_pReader; + + long long pos = start_offset; + const long long stop = start_offset + size; + + // For WebM files, there is a bias towards previous reference times + //(in order to support alt-ref frames, which refer back to the previous + // keyframe). Normally a 0 value is not possible, but here we tenatively + // allow 0 as the value of a reference frame, with the interpretation + // that this is a "previous" reference time. + + long long prev = 1; // nonce + long long next = 0; // nonce + long long duration = -1; // really, this is unsigned + + long long bpos = -1; + long long bsize = -1; + + while (pos < stop) { + long len; + const long long id = ReadID(pReader, pos, len); + if (id < 0 || (pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume ID + + const long long size = ReadUInt(pReader, pos, len); + assert(size >= 0); // TODO + assert((pos + len) <= stop); + + pos += len; // consume size + + if (id == libwebm::kMkvBlock) { + if (bpos < 0) { // Block ID + bpos = pos; + bsize = size; + } + } else if (id == libwebm::kMkvBlockDuration) { + if (size > 8) + return E_FILE_FORMAT_INVALID; + + duration = UnserializeUInt(pReader, pos, size); + + if (duration < 0) + return E_FILE_FORMAT_INVALID; + } else if (id == libwebm::kMkvReferenceBlock) { + if (size > 8 || size <= 0) + return E_FILE_FORMAT_INVALID; + const long size_ = static_cast<long>(size); + + long long time; + + long status = UnserializeInt(pReader, pos, size_, time); + assert(status == 0); + if (status != 0) + return -1; + + if (time <= 0) // see note above + prev = time; + else + next = time; + } + + pos += size; // consume payload + if (pos > stop) + return E_FILE_FORMAT_INVALID; + } + if (bpos < 0) + return E_FILE_FORMAT_INVALID; + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + assert(bsize >= 0); + + const long idx = m_entries_count; + + BlockEntry** const ppEntry = m_entries + idx; + BlockEntry*& pEntry = *ppEntry; + + pEntry = new (std::nothrow) + BlockGroup(this, idx, bpos, bsize, prev, next, duration, discard_padding); + + if (pEntry == NULL) + return -1; // generic error + + BlockGroup* const p = static_cast<BlockGroup*>(pEntry); + + const long status = p->Parse(); + + if (status == 0) { // success + ++m_entries_count; + return 0; + } + + delete pEntry; + pEntry = 0; + + return status; +} + +long Cluster::CreateSimpleBlock(long long st, long long sz) { + assert(m_entries); + assert(m_entries_size > 0); + assert(m_entries_count >= 0); + assert(m_entries_count < m_entries_size); + + const long idx = m_entries_count; + + BlockEntry** const ppEntry = m_entries + idx; + BlockEntry*& pEntry = *ppEntry; + + pEntry = new (std::nothrow) SimpleBlock(this, idx, st, sz); + + if (pEntry == NULL) + return -1; // generic error + + SimpleBlock* const p = static_cast<SimpleBlock*>(pEntry); + + const long status = p->Parse(); + + if (status == 0) { + ++m_entries_count; + return 0; + } + + delete pEntry; + pEntry = 0; + + return status; +} + +long Cluster::GetFirst(const BlockEntry*& pFirst) const { + if (m_entries_count <= 0) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) { // error + pFirst = NULL; + return status; + } + + if (m_entries_count <= 0) { // empty cluster + pFirst = NULL; + return 0; + } + } + + assert(m_entries); + + pFirst = m_entries[0]; + assert(pFirst); + + return 0; // success +} + +long Cluster::GetLast(const BlockEntry*& pLast) const { + for (;;) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) { // error + pLast = NULL; + return status; + } + + if (status > 0) // no new block + break; + } + + if (m_entries_count <= 0) { + pLast = NULL; + return 0; + } + + assert(m_entries); + + const long idx = m_entries_count - 1; + + pLast = m_entries[idx]; + assert(pLast); + + return 0; +} + +long Cluster::GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const { + assert(pCurr); + assert(m_entries); + assert(m_entries_count > 0); + + size_t idx = pCurr->GetIndex(); + assert(idx < size_t(m_entries_count)); + assert(m_entries[idx] == pCurr); + + ++idx; + + if (idx >= size_t(m_entries_count)) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) { // error + pNext = NULL; + return status; + } + + if (status > 0) { + pNext = NULL; + return 0; + } + + assert(m_entries); + assert(m_entries_count > 0); + assert(idx < size_t(m_entries_count)); + } + + pNext = m_entries[idx]; + assert(pNext); + + return 0; +} + +long Cluster::GetEntryCount() const { return m_entries_count; } + +const BlockEntry* Cluster::GetEntry(const Track* pTrack, + long long time_ns) const { + assert(pTrack); + + if (m_pSegment == NULL) // this is the special EOS cluster + return pTrack->GetEOS(); + + const BlockEntry* pResult = pTrack->GetEOS(); + + long index = 0; + + for (;;) { + if (index >= m_entries_count) { + long long pos; + long len; + + const long status = Parse(pos, len); + assert(status >= 0); + + if (status > 0) // completely parsed, and no more entries + return pResult; + + if (status < 0) // should never happen + return 0; + + assert(m_entries); + assert(index < m_entries_count); + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != pTrack->GetNumber()) { + ++index; + continue; + } + + if (pTrack->VetEntry(pEntry)) { + if (time_ns < 0) // just want first candidate block + return pEntry; + + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + + pResult = pEntry; // have a candidate + } else if (time_ns >= 0) { + const long long ns = pBlock->GetTime(this); + + if (ns > time_ns) + return pResult; + } + + ++index; + } +} + +const BlockEntry* Cluster::GetEntry(const CuePoint& cp, + const CuePoint::TrackPosition& tp) const { + assert(m_pSegment); + const long long tc = cp.GetTimeCode(); + + if (tp.m_block > 0) { + const long block = static_cast<long>(tp.m_block); + const long index = block - 1; + + while (index >= m_entries_count) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) // TODO: can this happen? + return NULL; + + if (status > 0) // nothing remains to be parsed + return NULL; + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if ((pBlock->GetTrackNumber() == tp.m_track) && + (pBlock->GetTimeCode(this) == tc)) { + return pEntry; + } + } + + long index = 0; + + for (;;) { + if (index >= m_entries_count) { + long long pos; + long len; + + const long status = Parse(pos, len); + + if (status < 0) // TODO: can this happen? + return NULL; + + if (status > 0) // nothing remains to be parsed + return NULL; + + assert(m_entries); + assert(index < m_entries_count); + } + + const BlockEntry* const pEntry = m_entries[index]; + assert(pEntry); + assert(!pEntry->EOS()); + + const Block* const pBlock = pEntry->GetBlock(); + assert(pBlock); + + if (pBlock->GetTrackNumber() != tp.m_track) { + ++index; + continue; + } + + const long long tc_ = pBlock->GetTimeCode(this); + + if (tc_ < tc) { + ++index; + continue; + } + + if (tc_ > tc) + return NULL; + + const Tracks* const pTracks = m_pSegment->GetTracks(); + assert(pTracks); + + const long tn = static_cast<long>(tp.m_track); + const Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + return NULL; + + const long long type = pTrack->GetType(); + + if (type == 2) // audio + return pEntry; + + if (type != 1) // not video + return NULL; + + if (!pBlock->IsKey()) + return NULL; + + return pEntry; + } +} + +BlockEntry::BlockEntry(Cluster* p, long idx) : m_pCluster(p), m_index(idx) {} +BlockEntry::~BlockEntry() {} +const Cluster* BlockEntry::GetCluster() const { return m_pCluster; } +long BlockEntry::GetIndex() const { return m_index; } + +SimpleBlock::SimpleBlock(Cluster* pCluster, long idx, long long start, + long long size) + : BlockEntry(pCluster, idx), m_block(start, size, 0) {} + +long SimpleBlock::Parse() { return m_block.Parse(m_pCluster); } +BlockEntry::Kind SimpleBlock::GetKind() const { return kBlockSimple; } +const Block* SimpleBlock::GetBlock() const { return &m_block; } + +BlockGroup::BlockGroup(Cluster* pCluster, long idx, long long block_start, + long long block_size, long long prev, long long next, + long long duration, long long discard_padding) + : BlockEntry(pCluster, idx), + m_block(block_start, block_size, discard_padding), + m_prev(prev), + m_next(next), + m_duration(duration) {} + +long BlockGroup::Parse() { + const long status = m_block.Parse(m_pCluster); + + if (status) + return status; + + m_block.SetKey((m_prev > 0) && (m_next <= 0)); + + return 0; +} + +BlockEntry::Kind BlockGroup::GetKind() const { return kBlockGroup; } +const Block* BlockGroup::GetBlock() const { return &m_block; } +long long BlockGroup::GetPrevTimeCode() const { return m_prev; } +long long BlockGroup::GetNextTimeCode() const { return m_next; } +long long BlockGroup::GetDurationTimeCode() const { return m_duration; } + +Block::Block(long long start, long long size_, long long discard_padding) + : m_start(start), + m_size(size_), + m_track(0), + m_timecode(-1), + m_flags(0), + m_frames(NULL), + m_frame_count(-1), + m_discard_padding(discard_padding) {} + +Block::~Block() { delete[] m_frames; } + +long Block::Parse(const Cluster* pCluster) { + if (pCluster == NULL) + return -1; + + if (pCluster->m_pSegment == NULL) + return -1; + + assert(m_start >= 0); + assert(m_size >= 0); + assert(m_track <= 0); + assert(m_frames == NULL); + assert(m_frame_count <= 0); + + long long pos = m_start; + const long long stop = m_start + m_size; + + long len; + + IMkvReader* const pReader = pCluster->m_pSegment->m_pReader; + + m_track = ReadUInt(pReader, pos, len); + + if (m_track <= 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume track number + + if ((stop - pos) < 2) + return E_FILE_FORMAT_INVALID; + + long status; + long long value; + + status = UnserializeInt(pReader, pos, 2, value); + + if (status) + return E_FILE_FORMAT_INVALID; + + if (value < SHRT_MIN) + return E_FILE_FORMAT_INVALID; + + if (value > SHRT_MAX) + return E_FILE_FORMAT_INVALID; + + m_timecode = static_cast<short>(value); + + pos += 2; + + if ((stop - pos) <= 0) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &m_flags); + + if (status) + return E_FILE_FORMAT_INVALID; + + const int lacing = int(m_flags & 0x06) >> 1; + + ++pos; // consume flags byte + + if (lacing == 0) { // no lacing + if (pos > stop) + return E_FILE_FORMAT_INVALID; + + m_frame_count = 1; + m_frames = new (std::nothrow) Frame[m_frame_count]; + if (m_frames == NULL) + return -1; + + Frame& f = m_frames[0]; + f.pos = pos; + + const long long frame_size = stop - pos; + + if (frame_size > LONG_MAX || frame_size <= 0) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast<long>(frame_size); + + return 0; // success + } + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + unsigned char biased_count; + + status = pReader->Read(pos, 1, &biased_count); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; // consume frame count + if (pos > stop) + return E_FILE_FORMAT_INVALID; + + m_frame_count = int(biased_count) + 1; + + m_frames = new (std::nothrow) Frame[m_frame_count]; + if (m_frames == NULL) + return -1; + + if (!m_frames) + return E_FILE_FORMAT_INVALID; + + if (lacing == 1) { // Xiph + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + long long size = 0; + int frame_count = m_frame_count; + + while (frame_count > 1) { + long frame_size = 0; + + for (;;) { + unsigned char val; + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + status = pReader->Read(pos, 1, &val); + + if (status) + return E_FILE_FORMAT_INVALID; + + ++pos; // consume xiph size byte + + frame_size += val; + + if (val < 255) + break; + } + + Frame& f = *pf++; + assert(pf < pf_end); + if (pf >= pf_end) + return E_FILE_FORMAT_INVALID; + + f.pos = 0; // patch later + + if (frame_size <= 0) + return E_FILE_FORMAT_INVALID; + + f.len = frame_size; + size += frame_size; // contribution of this frame + + --frame_count; + } + + if (pf >= pf_end || pos > stop) + return E_FILE_FORMAT_INVALID; + + { + Frame& f = *pf++; + + if (pf != pf_end) + return E_FILE_FORMAT_INVALID; + + f.pos = 0; // patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size - size; + + if (frame_size > LONG_MAX || frame_size <= 0) + return E_FILE_FORMAT_INVALID; + + f.len = static_cast<long>(frame_size); + } + + pf = m_frames; + while (pf != pf_end) { + Frame& f = *pf++; + assert((pos + f.len) <= stop); + + if ((pos + f.len) > stop) + return E_FILE_FORMAT_INVALID; + + f.pos = pos; + pos += f.len; + } + + assert(pos == stop); + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + } else if (lacing == 2) { // fixed-size lacing + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + const long long total_size = stop - pos; + + if ((total_size % m_frame_count) != 0) + return E_FILE_FORMAT_INVALID; + + const long long frame_size = total_size / m_frame_count; + + if (frame_size > LONG_MAX || frame_size <= 0) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + while (pf != pf_end) { + assert((pos + frame_size) <= stop); + if ((pos + frame_size) > stop) + return E_FILE_FORMAT_INVALID; + + Frame& f = *pf++; + + f.pos = pos; + f.len = static_cast<long>(frame_size); + + pos += frame_size; + } + + assert(pos == stop); + if (pos != stop) + return E_FILE_FORMAT_INVALID; + + } else { + assert(lacing == 3); // EBML lacing + + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + long long size = 0; + int frame_count = m_frame_count; + + long long frame_size = ReadUInt(pReader, pos, len); + + if (frame_size <= 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume length of size of first frame + + if ((pos + frame_size) > stop) + return E_FILE_FORMAT_INVALID; + + Frame* pf = m_frames; + Frame* const pf_end = pf + m_frame_count; + + { + Frame& curr = *pf; + + curr.pos = 0; // patch later + + curr.len = static_cast<long>(frame_size); + size += curr.len; // contribution of this frame + } + + --frame_count; + + while (frame_count > 1) { + if (pos >= stop) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + if (pf >= pf_end) + return E_FILE_FORMAT_INVALID; + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + if (prev.len != frame_size) + return E_FILE_FORMAT_INVALID; + + assert(pf < pf_end); + if (pf >= pf_end) + return E_FILE_FORMAT_INVALID; + + Frame& curr = *pf; + + curr.pos = 0; // patch later + + const long long delta_size_ = ReadUInt(pReader, pos, len); + + if (delta_size_ < 0) + return E_FILE_FORMAT_INVALID; + + if ((pos + len) > stop) + return E_FILE_FORMAT_INVALID; + + pos += len; // consume length of (delta) size + if (pos > stop) + return E_FILE_FORMAT_INVALID; + + const long exp = 7 * len - 1; + const long long bias = (1LL << exp) - 1LL; + const long long delta_size = delta_size_ - bias; + + frame_size += delta_size; + + if (frame_size <= 0) + return E_FILE_FORMAT_INVALID; + + if (frame_size > LONG_MAX) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast<long>(frame_size); + // Check if size + curr.len could overflow. + if (size > LLONG_MAX - curr.len) { + return E_FILE_FORMAT_INVALID; + } + size += curr.len; // contribution of this frame + + --frame_count; + } + + // parse last frame + if (frame_count > 0) { + if (pos > stop || pf >= pf_end) + return E_FILE_FORMAT_INVALID; + + const Frame& prev = *pf++; + assert(prev.len == frame_size); + if (prev.len != frame_size) + return E_FILE_FORMAT_INVALID; + + if (pf >= pf_end) + return E_FILE_FORMAT_INVALID; + + Frame& curr = *pf++; + if (pf != pf_end) + return E_FILE_FORMAT_INVALID; + + curr.pos = 0; // patch later + + const long long total_size = stop - pos; + + if (total_size < size) + return E_FILE_FORMAT_INVALID; + + frame_size = total_size - size; + + if (frame_size > LONG_MAX || frame_size <= 0) + return E_FILE_FORMAT_INVALID; + + curr.len = static_cast<long>(frame_size); + } + + pf = m_frames; + while (pf != pf_end) { + Frame& f = *pf++; + if ((pos + f.len) > stop) + return E_FILE_FORMAT_INVALID; + + f.pos = pos; + pos += f.len; + } + + if (pos != stop) + return E_FILE_FORMAT_INVALID; + } + + return 0; // success +} + +long long Block::GetTimeCode(const Cluster* pCluster) const { + if (pCluster == 0) + return m_timecode; + + const long long tc0 = pCluster->GetTimeCode(); + assert(tc0 >= 0); + + // Check if tc0 + m_timecode would overflow. + if (tc0 < 0 || LLONG_MAX - tc0 < m_timecode) { + return -1; + } + + const long long tc = tc0 + m_timecode; + + return tc; // unscaled timecode units +} + +long long Block::GetTime(const Cluster* pCluster) const { + assert(pCluster); + + const long long tc = GetTimeCode(pCluster); + + const Segment* const pSegment = pCluster->m_pSegment; + const SegmentInfo* const pInfo = pSegment->GetInfo(); + assert(pInfo); + + const long long scale = pInfo->GetTimeCodeScale(); + assert(scale >= 1); + + // Check if tc * scale could overflow. + if (tc != 0 && scale > LLONG_MAX / tc) { + return -1; + } + const long long ns = tc * scale; + + return ns; +} + +long long Block::GetTrackNumber() const { return m_track; } + +bool Block::IsKey() const { + return ((m_flags & static_cast<unsigned char>(1 << 7)) != 0); +} + +void Block::SetKey(bool bKey) { + if (bKey) + m_flags |= static_cast<unsigned char>(1 << 7); + else + m_flags &= 0x7F; +} + +bool Block::IsInvisible() const { return bool(int(m_flags & 0x08) != 0); } + +Block::Lacing Block::GetLacing() const { + const int value = int(m_flags & 0x06) >> 1; + return static_cast<Lacing>(value); +} + +int Block::GetFrameCount() const { return m_frame_count; } + +const Block::Frame& Block::GetFrame(int idx) const { + assert(idx >= 0); + assert(idx < m_frame_count); + + const Frame& f = m_frames[idx]; + assert(f.pos > 0); + assert(f.len > 0); + + return f; +} + +long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const { + assert(pReader); + assert(buf); + + const long status = pReader->Read(pos, len, buf); + return status; +} + +long long Block::GetDiscardPadding() const { return m_discard_padding; } + +} // namespace mkvparser diff --git a/mkvparser/mkvparser.h b/mkvparser/mkvparser.h new file mode 100644 index 0000000..848d01f --- /dev/null +++ b/mkvparser/mkvparser.h @@ -0,0 +1,1147 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef MKVPARSER_MKVPARSER_H_ +#define MKVPARSER_MKVPARSER_H_ + +#include <cstddef> + +namespace mkvparser { + +const int E_PARSE_FAILED = -1; +const int E_FILE_FORMAT_INVALID = -2; +const int E_BUFFER_NOT_FULL = -3; + +class IMkvReader { + public: + virtual int Read(long long pos, long len, unsigned char* buf) = 0; + virtual int Length(long long* total, long long* available) = 0; + + protected: + virtual ~IMkvReader() {} +}; + +template <typename Type> +Type* SafeArrayAlloc(unsigned long long num_elements, + unsigned long long element_size); +long long GetUIntLength(IMkvReader*, long long, long&); +long long ReadUInt(IMkvReader*, long long, long&); +long long ReadID(IMkvReader* pReader, long long pos, long& len); +long long UnserializeUInt(IMkvReader*, long long pos, long long size); + +long UnserializeFloat(IMkvReader*, long long pos, long long size, double&); +long UnserializeInt(IMkvReader*, long long pos, long long size, + long long& result); + +long UnserializeString(IMkvReader*, long long pos, long long size, char*& str); + +long ParseElementHeader(IMkvReader* pReader, + long long& pos, // consume id and size fields + long long stop, // if you know size of element's parent + long long& id, long long& size); + +bool Match(IMkvReader*, long long&, unsigned long, long long&); +bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&); + +void GetVersion(int& major, int& minor, int& build, int& revision); + +struct EBMLHeader { + EBMLHeader(); + ~EBMLHeader(); + long long m_version; + long long m_readVersion; + long long m_maxIdLength; + long long m_maxSizeLength; + char* m_docType; + long long m_docTypeVersion; + long long m_docTypeReadVersion; + + long long Parse(IMkvReader*, long long&); + void Init(); +}; + +class Segment; +class Track; +class Cluster; + +class Block { + Block(const Block&); + Block& operator=(const Block&); + + public: + const long long m_start; + const long long m_size; + + Block(long long start, long long size, long long discard_padding); + ~Block(); + + long Parse(const Cluster*); + + long long GetTrackNumber() const; + long long GetTimeCode(const Cluster*) const; // absolute, but not scaled + long long GetTime(const Cluster*) const; // absolute, and scaled (ns) + bool IsKey() const; + void SetKey(bool); + bool IsInvisible() const; + + enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml }; + Lacing GetLacing() const; + + int GetFrameCount() const; // to index frames: [0, count) + + struct Frame { + long long pos; // absolute offset + long len; + + long Read(IMkvReader*, unsigned char*) const; + }; + + const Frame& GetFrame(int frame_index) const; + + long long GetDiscardPadding() const; + + private: + long long m_track; // Track::Number() + short m_timecode; // relative to cluster + unsigned char m_flags; + + Frame* m_frames; + int m_frame_count; + + protected: + const long long m_discard_padding; +}; + +class BlockEntry { + BlockEntry(const BlockEntry&); + BlockEntry& operator=(const BlockEntry&); + + protected: + BlockEntry(Cluster*, long index); + + public: + virtual ~BlockEntry(); + + bool EOS() const { return (GetKind() == kBlockEOS); } + const Cluster* GetCluster() const; + long GetIndex() const; + virtual const Block* GetBlock() const = 0; + + enum Kind { kBlockEOS, kBlockSimple, kBlockGroup }; + virtual Kind GetKind() const = 0; + + protected: + Cluster* const m_pCluster; + const long m_index; +}; + +class SimpleBlock : public BlockEntry { + SimpleBlock(const SimpleBlock&); + SimpleBlock& operator=(const SimpleBlock&); + + public: + SimpleBlock(Cluster*, long index, long long start, long long size); + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + + protected: + Block m_block; +}; + +class BlockGroup : public BlockEntry { + BlockGroup(const BlockGroup&); + BlockGroup& operator=(const BlockGroup&); + + public: + BlockGroup(Cluster*, long index, + long long block_start, // absolute pos of block's payload + long long block_size, // size of block's payload + long long prev, long long next, long long duration, + long long discard_padding); + + long Parse(); + + Kind GetKind() const; + const Block* GetBlock() const; + + long long GetPrevTimeCode() const; // relative to block's time + long long GetNextTimeCode() const; // as above + long long GetDurationTimeCode() const; + + private: + Block m_block; + const long long m_prev; + const long long m_next; + const long long m_duration; +}; + +/////////////////////////////////////////////////////////////// +// ContentEncoding element +// Elements used to describe if the track data has been encrypted or +// compressed with zlib or header stripping. +class ContentEncoding { + public: + enum { kCTR = 1 }; + + ContentEncoding(); + ~ContentEncoding(); + + // ContentCompression element names + struct ContentCompression { + ContentCompression(); + ~ContentCompression(); + + unsigned long long algo; + unsigned char* settings; + long long settings_len; + }; + + // ContentEncAESSettings element names + struct ContentEncAESSettings { + ContentEncAESSettings() : cipher_mode(kCTR) {} + ~ContentEncAESSettings() {} + + unsigned long long cipher_mode; + }; + + // ContentEncryption element names + struct ContentEncryption { + ContentEncryption(); + ~ContentEncryption(); + + unsigned long long algo; + unsigned char* key_id; + long long key_id_len; + unsigned char* signature; + long long signature_len; + unsigned char* sig_key_id; + long long sig_key_id_len; + unsigned long long sig_algo; + unsigned long long sig_hash_algo; + + ContentEncAESSettings aes_settings; + }; + + // Returns ContentCompression represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentCompression* GetCompressionByIndex(unsigned long idx) const; + + // Returns number of ContentCompression elements in this ContentEncoding + // element. + unsigned long GetCompressionCount() const; + + // Parses the ContentCompression element from |pReader|. |start| is the + // starting offset of the ContentCompression payload. |size| is the size in + // bytes of the ContentCompression payload. |compression| is where the parsed + // values will be stored. + long ParseCompressionEntry(long long start, long long size, + IMkvReader* pReader, + ContentCompression* compression); + + // Returns ContentEncryption represented by |idx|. Returns NULL if |idx| + // is out of bounds. + const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const; + + // Returns number of ContentEncryption elements in this ContentEncoding + // element. + unsigned long GetEncryptionCount() const; + + // Parses the ContentEncAESSettings element from |pReader|. |start| is the + // starting offset of the ContentEncAESSettings payload. |size| is the + // size in bytes of the ContentEncAESSettings payload. |encryption| is + // where the parsed values will be stored. + long ParseContentEncAESSettingsEntry(long long start, long long size, + IMkvReader* pReader, + ContentEncAESSettings* aes); + + // Parses the ContentEncoding element from |pReader|. |start| is the + // starting offset of the ContentEncoding payload. |size| is the size in + // bytes of the ContentEncoding payload. Returns true on success. + long ParseContentEncodingEntry(long long start, long long size, + IMkvReader* pReader); + + // Parses the ContentEncryption element from |pReader|. |start| is the + // starting offset of the ContentEncryption payload. |size| is the size in + // bytes of the ContentEncryption payload. |encryption| is where the parsed + // values will be stored. + long ParseEncryptionEntry(long long start, long long size, + IMkvReader* pReader, ContentEncryption* encryption); + + unsigned long long encoding_order() const { return encoding_order_; } + unsigned long long encoding_scope() const { return encoding_scope_; } + unsigned long long encoding_type() const { return encoding_type_; } + + private: + // Member variables for list of ContentCompression elements. + ContentCompression** compression_entries_; + ContentCompression** compression_entries_end_; + + // Member variables for list of ContentEncryption elements. + ContentEncryption** encryption_entries_; + ContentEncryption** encryption_entries_end_; + + // ContentEncoding element names + unsigned long long encoding_order_; + unsigned long long encoding_scope_; + unsigned long long encoding_type_; + + // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding); + ContentEncoding(const ContentEncoding&); + ContentEncoding& operator=(const ContentEncoding&); +}; + +class Track { + Track(const Track&); + Track& operator=(const Track&); + + public: + class Info; + static long Create(Segment*, const Info&, long long element_start, + long long element_size, Track*&); + + enum Type { kVideo = 1, kAudio = 2, kSubtitle = 0x11, kMetadata = 0x21 }; + + Segment* const m_pSegment; + const long long m_element_start; + const long long m_element_size; + virtual ~Track(); + + long GetType() const; + long GetNumber() const; + unsigned long long GetUid() const; + const char* GetNameAsUTF8() const; + const char* GetLanguage() const; + const char* GetCodecNameAsUTF8() const; + const char* GetCodecId() const; + const unsigned char* GetCodecPrivate(size_t&) const; + bool GetLacing() const; + unsigned long long GetDefaultDuration() const; + unsigned long long GetCodecDelay() const; + unsigned long long GetSeekPreRoll() const; + + const BlockEntry* GetEOS() const; + + struct Settings { + long long start; + long long size; + }; + + class Info { + public: + Info(); + ~Info(); + int Copy(Info&) const; + void Clear(); + long type; + long number; + unsigned long long uid; + unsigned long long defaultDuration; + unsigned long long codecDelay; + unsigned long long seekPreRoll; + char* nameAsUTF8; + char* language; + char* codecId; + char* codecNameAsUTF8; + unsigned char* codecPrivate; + size_t codecPrivateSize; + bool lacing; + Settings settings; + + private: + Info(const Info&); + Info& operator=(const Info&); + int CopyStr(char* Info::*str, Info&) const; + }; + + long GetFirst(const BlockEntry*&) const; + long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const; + virtual bool VetEntry(const BlockEntry*) const; + virtual long Seek(long long time_ns, const BlockEntry*&) const; + + const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const; + unsigned long GetContentEncodingCount() const; + + long ParseContentEncodingsEntry(long long start, long long size); + + protected: + Track(Segment*, long long element_start, long long element_size); + + Info m_info; + + class EOSBlock : public BlockEntry { + public: + EOSBlock(); + + Kind GetKind() const; + const Block* GetBlock() const; + }; + + EOSBlock m_eos; + + private: + ContentEncoding** content_encoding_entries_; + ContentEncoding** content_encoding_entries_end_; +}; + +struct PrimaryChromaticity { + PrimaryChromaticity() : x(0), y(0) {} + ~PrimaryChromaticity() {} + static bool Parse(IMkvReader* reader, long long read_pos, + long long value_size, bool is_x, + PrimaryChromaticity** chromaticity); + float x; + float y; +}; + +struct MasteringMetadata { + static const float kValueNotPresent; + + MasteringMetadata() + : r(NULL), + g(NULL), + b(NULL), + white_point(NULL), + luminance_max(kValueNotPresent), + luminance_min(kValueNotPresent) {} + ~MasteringMetadata() { + delete r; + delete g; + delete b; + delete white_point; + } + + static bool Parse(IMkvReader* reader, long long element_start, + long long element_size, + MasteringMetadata** mastering_metadata); + + PrimaryChromaticity* r; + PrimaryChromaticity* g; + PrimaryChromaticity* b; + PrimaryChromaticity* white_point; + float luminance_max; + float luminance_min; +}; + +struct Colour { + static const long long kValueNotPresent; + + // Unless otherwise noted all values assigned upon construction are the + // equivalent of unspecified/default. + Colour() + : matrix_coefficients(kValueNotPresent), + bits_per_channel(kValueNotPresent), + chroma_subsampling_horz(kValueNotPresent), + chroma_subsampling_vert(kValueNotPresent), + cb_subsampling_horz(kValueNotPresent), + cb_subsampling_vert(kValueNotPresent), + chroma_siting_horz(kValueNotPresent), + chroma_siting_vert(kValueNotPresent), + range(kValueNotPresent), + transfer_characteristics(kValueNotPresent), + primaries(kValueNotPresent), + max_cll(kValueNotPresent), + max_fall(kValueNotPresent), + mastering_metadata(NULL) {} + ~Colour() { + delete mastering_metadata; + mastering_metadata = NULL; + } + + static bool Parse(IMkvReader* reader, long long element_start, + long long element_size, Colour** colour); + + long long matrix_coefficients; + long long bits_per_channel; + long long chroma_subsampling_horz; + long long chroma_subsampling_vert; + long long cb_subsampling_horz; + long long cb_subsampling_vert; + long long chroma_siting_horz; + long long chroma_siting_vert; + long long range; + long long transfer_characteristics; + long long primaries; + long long max_cll; + long long max_fall; + + MasteringMetadata* mastering_metadata; +}; + +struct Projection { + enum ProjectionType { + kTypeNotPresent = -1, + kRectangular = 0, + kEquirectangular = 1, + kCubeMap = 2, + kMesh = 3, + }; + static const float kValueNotPresent; + Projection() + : type(kTypeNotPresent), + private_data(NULL), + private_data_length(0), + pose_yaw(kValueNotPresent), + pose_pitch(kValueNotPresent), + pose_roll(kValueNotPresent) {} + ~Projection() { delete[] private_data; } + static bool Parse(IMkvReader* reader, long long element_start, + long long element_size, Projection** projection); + + ProjectionType type; + unsigned char* private_data; + size_t private_data_length; + float pose_yaw; + float pose_pitch; + float pose_roll; +}; + +class VideoTrack : public Track { + VideoTrack(const VideoTrack&); + VideoTrack& operator=(const VideoTrack&); + + VideoTrack(Segment*, long long element_start, long long element_size); + + public: + virtual ~VideoTrack(); + static long Parse(Segment*, const Info&, long long element_start, + long long element_size, VideoTrack*&); + + long long GetWidth() const; + long long GetHeight() const; + long long GetDisplayWidth() const; + long long GetDisplayHeight() const; + long long GetDisplayUnit() const; + long long GetStereoMode() const; + double GetFrameRate() const; + + bool VetEntry(const BlockEntry*) const; + long Seek(long long time_ns, const BlockEntry*&) const; + + Colour* GetColour() const; + + Projection* GetProjection() const; + + const char* GetColourSpace() const { return m_colour_space; } + + private: + long long m_width; + long long m_height; + long long m_display_width; + long long m_display_height; + long long m_display_unit; + long long m_stereo_mode; + char* m_colour_space; + double m_rate; + + Colour* m_colour; + Projection* m_projection; +}; + +class AudioTrack : public Track { + AudioTrack(const AudioTrack&); + AudioTrack& operator=(const AudioTrack&); + + AudioTrack(Segment*, long long element_start, long long element_size); + + public: + static long Parse(Segment*, const Info&, long long element_start, + long long element_size, AudioTrack*&); + + double GetSamplingRate() const; + long long GetChannels() const; + long long GetBitDepth() const; + + private: + double m_rate; + long long m_channels; + long long m_bitDepth; +}; + +class Tracks { + Tracks(const Tracks&); + Tracks& operator=(const Tracks&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Tracks(Segment*, long long start, long long size, long long element_start, + long long element_size); + + ~Tracks(); + + long Parse(); + + unsigned long GetTracksCount() const; + + const Track* GetTrackByNumber(long tn) const; + const Track* GetTrackByIndex(unsigned long idx) const; + + private: + Track** m_trackEntries; + Track** m_trackEntriesEnd; + + long ParseTrackEntry(long long payload_start, long long payload_size, + long long element_start, long long element_size, + Track*&) const; +}; + +class Chapters { + Chapters(const Chapters&); + Chapters& operator=(const Chapters&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Chapters(Segment*, long long payload_start, long long payload_size, + long long element_start, long long element_size); + + ~Chapters(); + + long Parse(); + + class Atom; + class Edition; + + class Display { + friend class Atom; + Display(); + Display(const Display&); + ~Display(); + Display& operator=(const Display&); + + public: + const char* GetString() const; + const char* GetLanguage() const; + const char* GetCountry() const; + + private: + void Init(); + void ShallowCopy(Display&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + char* m_string; + char* m_language; + char* m_country; + }; + + class Atom { + friend class Edition; + Atom(); + Atom(const Atom&); + ~Atom(); + Atom& operator=(const Atom&); + + public: + unsigned long long GetUID() const; + const char* GetStringUID() const; + + long long GetStartTimecode() const; + long long GetStopTimecode() const; + + long long GetStartTime(const Chapters*) const; + long long GetStopTime(const Chapters*) const; + + int GetDisplayCount() const; + const Display* GetDisplay(int index) const; + + private: + void Init(); + void ShallowCopy(Atom&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + static long long GetTime(const Chapters*, long long timecode); + + long ParseDisplay(IMkvReader*, long long pos, long long size); + bool ExpandDisplaysArray(); + + char* m_string_uid; + unsigned long long m_uid; + long long m_start_timecode; + long long m_stop_timecode; + + Display* m_displays; + int m_displays_size; + int m_displays_count; + }; + + class Edition { + friend class Chapters; + Edition(); + Edition(const Edition&); + ~Edition(); + Edition& operator=(const Edition&); + + public: + int GetAtomCount() const; + const Atom* GetAtom(int index) const; + + private: + void Init(); + void ShallowCopy(Edition&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + long ParseAtom(IMkvReader*, long long pos, long long size); + bool ExpandAtomsArray(); + + Atom* m_atoms; + int m_atoms_size; + int m_atoms_count; + }; + + int GetEditionCount() const; + const Edition* GetEdition(int index) const; + + private: + long ParseEdition(long long pos, long long size); + bool ExpandEditionsArray(); + + Edition* m_editions; + int m_editions_size; + int m_editions_count; +}; + +class Tags { + Tags(const Tags&); + Tags& operator=(const Tags&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + Tags(Segment*, long long payload_start, long long payload_size, + long long element_start, long long element_size); + + ~Tags(); + + long Parse(); + + class Tag; + class SimpleTag; + + class SimpleTag { + friend class Tag; + SimpleTag(); + SimpleTag(const SimpleTag&); + ~SimpleTag(); + SimpleTag& operator=(const SimpleTag&); + + public: + const char* GetTagName() const; + const char* GetTagString() const; + + private: + void Init(); + void ShallowCopy(SimpleTag&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + char* m_tag_name; + char* m_tag_string; + }; + + class Tag { + friend class Tags; + Tag(); + Tag(const Tag&); + ~Tag(); + Tag& operator=(const Tag&); + + public: + int GetSimpleTagCount() const; + const SimpleTag* GetSimpleTag(int index) const; + + private: + void Init(); + void ShallowCopy(Tag&) const; + void Clear(); + long Parse(IMkvReader*, long long pos, long long size); + + long ParseSimpleTag(IMkvReader*, long long pos, long long size); + bool ExpandSimpleTagsArray(); + + SimpleTag* m_simple_tags; + int m_simple_tags_size; + int m_simple_tags_count; + }; + + int GetTagCount() const; + const Tag* GetTag(int index) const; + + private: + long ParseTag(long long pos, long long size); + bool ExpandTagsArray(); + + Tag* m_tags; + int m_tags_size; + int m_tags_count; +}; + +class SegmentInfo { + SegmentInfo(const SegmentInfo&); + SegmentInfo& operator=(const SegmentInfo&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SegmentInfo(Segment*, long long start, long long size, + long long element_start, long long element_size); + + ~SegmentInfo(); + + long Parse(); + + long long GetTimeCodeScale() const; + long long GetDuration() const; // scaled + const char* GetMuxingAppAsUTF8() const; + const char* GetWritingAppAsUTF8() const; + const char* GetTitleAsUTF8() const; + + private: + long long m_timecodeScale; + double m_duration; + char* m_pMuxingAppAsUTF8; + char* m_pWritingAppAsUTF8; + char* m_pTitleAsUTF8; +}; + +class SeekHead { + SeekHead(const SeekHead&); + SeekHead& operator=(const SeekHead&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + SeekHead(Segment*, long long start, long long size, long long element_start, + long long element_size); + + ~SeekHead(); + + long Parse(); + + struct Entry { + Entry(); + + // the SeekHead entry payload + long long id; + long long pos; + + // absolute pos of SeekEntry ID + long long element_start; + + // SeekEntry ID size + size size + payload + long long element_size; + }; + + int GetCount() const; + const Entry* GetEntry(int idx) const; + + struct VoidElement { + // absolute pos of Void ID + long long element_start; + + // ID size + size size + payload size + long long element_size; + }; + + int GetVoidElementCount() const; + const VoidElement* GetVoidElement(int idx) const; + + private: + Entry* m_entries; + int m_entry_count; + + VoidElement* m_void_elements; + int m_void_element_count; + + static bool ParseEntry(IMkvReader*, + long long pos, // payload + long long size, Entry*); +}; + +class Cues; +class CuePoint { + friend class Cues; + + CuePoint(long, long long); + ~CuePoint(); + + CuePoint(const CuePoint&); + CuePoint& operator=(const CuePoint&); + + public: + long long m_element_start; + long long m_element_size; + + bool Load(IMkvReader*); + + long long GetTimeCode() const; // absolute but unscaled + long long GetTime(const Segment*) const; // absolute and scaled (ns units) + + struct TrackPosition { + long long m_track; + long long m_pos; // of cluster + long long m_block; + // codec_state //defaults to 0 + // reference = clusters containing req'd referenced blocks + // reftime = timecode of the referenced block + + bool Parse(IMkvReader*, long long, long long); + }; + + const TrackPosition* Find(const Track*) const; + + private: + const long m_index; + long long m_timecode; + TrackPosition* m_track_positions; + size_t m_track_positions_count; +}; + +class Cues { + friend class Segment; + + Cues(Segment*, long long start, long long size, long long element_start, + long long element_size); + ~Cues(); + + Cues(const Cues&); + Cues& operator=(const Cues&); + + public: + Segment* const m_pSegment; + const long long m_start; + const long long m_size; + const long long m_element_start; + const long long m_element_size; + + bool Find( // lower bound of time_ns + long long time_ns, const Track*, const CuePoint*&, + const CuePoint::TrackPosition*&) const; + + const CuePoint* GetFirst() const; + const CuePoint* GetLast() const; + const CuePoint* GetNext(const CuePoint*) const; + + const BlockEntry* GetBlock(const CuePoint*, + const CuePoint::TrackPosition*) const; + + bool LoadCuePoint() const; + long GetCount() const; // loaded only + // long GetTotal() const; //loaded + preloaded + bool DoneParsing() const; + + private: + bool Init() const; + bool PreloadCuePoint(long&, long long) const; + + mutable CuePoint** m_cue_points; + mutable long m_count; + mutable long m_preload_count; + mutable long long m_pos; +}; + +class Cluster { + friend class Segment; + + Cluster(const Cluster&); + Cluster& operator=(const Cluster&); + + public: + Segment* const m_pSegment; + + public: + static Cluster* Create(Segment*, + long index, // index in segment + long long off); // offset relative to segment + // long long element_size); + + Cluster(); // EndOfStream + ~Cluster(); + + bool EOS() const; + + long long GetTimeCode() const; // absolute, but not scaled + long long GetTime() const; // absolute, and scaled (nanosecond units) + long long GetFirstTime() const; // time (ns) of first (earliest) block + long long GetLastTime() const; // time (ns) of last (latest) block + + long GetFirst(const BlockEntry*&) const; + long GetLast(const BlockEntry*&) const; + long GetNext(const BlockEntry* curr, const BlockEntry*& next) const; + + const BlockEntry* GetEntry(const Track*, long long ns = -1) const; + const BlockEntry* GetEntry(const CuePoint&, + const CuePoint::TrackPosition&) const; + // const BlockEntry* GetMaxKey(const VideoTrack*) const; + + // static bool HasBlockEntries(const Segment*, long long); + + static long HasBlockEntries(const Segment*, long long idoff, long long& pos, + long& size); + + long GetEntryCount() const; + + long Load(long long& pos, long& size) const; + + long Parse(long long& pos, long& size) const; + long GetEntry(long index, const mkvparser::BlockEntry*&) const; + + protected: + Cluster(Segment*, long index, long long element_start); + // long long element_size); + + public: + const long long m_element_start; + long long GetPosition() const; // offset relative to segment + + long GetIndex() const; + long long GetElementSize() const; + // long long GetPayloadSize() const; + + // long long Unparsed() const; + + private: + long m_index; + mutable long long m_pos; + // mutable long long m_size; + mutable long long m_element_size; + mutable long long m_timecode; + mutable BlockEntry** m_entries; + mutable long m_entries_size; + mutable long m_entries_count; + + long ParseSimpleBlock(long long, long long&, long&); + long ParseBlockGroup(long long, long long&, long&); + + long CreateBlock(long long id, long long pos, long long size, + long long discard_padding); + long CreateBlockGroup(long long start_offset, long long size, + long long discard_padding); + long CreateSimpleBlock(long long, long long); +}; + +class Segment { + friend class Cues; + friend class Track; + friend class VideoTrack; + + Segment(const Segment&); + Segment& operator=(const Segment&); + + private: + Segment(IMkvReader*, long long elem_start, + // long long elem_size, + long long pos, long long size); + + public: + IMkvReader* const m_pReader; + const long long m_element_start; + // const long long m_element_size; + const long long m_start; // posn of segment payload + const long long m_size; // size of segment payload + Cluster m_eos; // TODO: make private? + + static long long CreateInstance(IMkvReader*, long long, Segment*&); + ~Segment(); + + long Load(); // loads headers and all clusters + + // for incremental loading + // long long Unparsed() const; + bool DoneParsing() const; + long long ParseHeaders(); // stops when first cluster is found + // long FindNextCluster(long long& pos, long& size) const; + long LoadCluster(long long& pos, long& size); // load one cluster + long LoadCluster(); + + long ParseNext(const Cluster* pCurr, const Cluster*& pNext, long long& pos, + long& size); + + const SeekHead* GetSeekHead() const; + const Tracks* GetTracks() const; + const SegmentInfo* GetInfo() const; + const Cues* GetCues() const; + const Chapters* GetChapters() const; + const Tags* GetTags() const; + + long long GetDuration() const; + + unsigned long GetCount() const; + const Cluster* GetFirst() const; + const Cluster* GetLast() const; + const Cluster* GetNext(const Cluster*); + + const Cluster* FindCluster(long long time_nanoseconds) const; + // const BlockEntry* Seek(long long time_nanoseconds, const Track*) const; + + const Cluster* FindOrPreloadCluster(long long pos); + + long ParseCues(long long cues_off, // offset relative to start of segment + long long& parse_pos, long& parse_len); + + private: + long long m_pos; // absolute file posn; what has been consumed so far + Cluster* m_pUnknownSize; + + SeekHead* m_pSeekHead; + SegmentInfo* m_pInfo; + Tracks* m_pTracks; + Cues* m_pCues; + Chapters* m_pChapters; + Tags* m_pTags; + Cluster** m_clusters; + long m_clusterCount; // number of entries for which m_index >= 0 + long m_clusterPreloadCount; // number of entries for which m_index < 0 + long m_clusterSize; // array size + + long DoLoadCluster(long long&, long&); + long DoLoadClusterUnknownSize(long long&, long&); + long DoParseNext(const Cluster*&, long long&, long&); + + bool AppendCluster(Cluster*); + bool PreloadCluster(Cluster*, ptrdiff_t); + + // void ParseSeekHead(long long pos, long long size); + // void ParseSeekEntry(long long pos, long long size); + // void ParseCues(long long); + + const BlockEntry* GetBlock(const CuePoint&, const CuePoint::TrackPosition&); +}; + +} // namespace mkvparser + +inline long mkvparser::Segment::LoadCluster() { + long long pos; + long size; + + return LoadCluster(pos, size); +} + +#endif // MKVPARSER_MKVPARSER_H_ diff --git a/mkvparser/mkvreader.cc b/mkvparser/mkvreader.cc new file mode 100644 index 0000000..9d19c1b --- /dev/null +++ b/mkvparser/mkvreader.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2010 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "mkvparser/mkvreader.h" + +#include <sys/types.h> + +#include <cassert> + +namespace mkvparser { + +MkvReader::MkvReader() : m_file(NULL), reader_owns_file_(true) {} + +MkvReader::MkvReader(FILE* fp) : m_file(fp), reader_owns_file_(false) { + GetFileSize(); +} + +MkvReader::~MkvReader() { + if (reader_owns_file_) + Close(); + m_file = NULL; +} + +int MkvReader::Open(const char* fileName) { + if (fileName == NULL) + return -1; + + if (m_file) + return -1; + +#ifdef _MSC_VER + const errno_t e = fopen_s(&m_file, fileName, "rb"); + + if (e) + return -1; // error +#else + m_file = fopen(fileName, "rb"); + + if (m_file == NULL) + return -1; +#endif + return !GetFileSize(); +} + +bool MkvReader::GetFileSize() { + if (m_file == NULL) + return false; +#ifdef _MSC_VER + int status = _fseeki64(m_file, 0L, SEEK_END); + + if (status) + return false; // error + + m_length = _ftelli64(m_file); +#else + fseek(m_file, 0L, SEEK_END); + m_length = ftell(m_file); +#endif + assert(m_length >= 0); + + if (m_length < 0) + return false; + +#ifdef _MSC_VER + status = _fseeki64(m_file, 0L, SEEK_SET); + + if (status) + return false; // error +#else + fseek(m_file, 0L, SEEK_SET); +#endif + + return true; +} + +void MkvReader::Close() { + if (m_file != NULL) { + fclose(m_file); + m_file = NULL; + } +} + +int MkvReader::Length(long long* total, long long* available) { + if (m_file == NULL) + return -1; + + if (total) + *total = m_length; + + if (available) + *available = m_length; + + return 0; +} + +int MkvReader::Read(long long offset, long len, unsigned char* buffer) { + if (m_file == NULL) + return -1; + + if (offset < 0) + return -1; + + if (len < 0) + return -1; + + if (len == 0) + return 0; + + if (offset >= m_length) + return -1; + +#ifdef _MSC_VER + const int status = _fseeki64(m_file, offset, SEEK_SET); + + if (status) + return -1; // error +#elif defined(_WIN32) + fseeko64(m_file, static_cast<off_t>(offset), SEEK_SET); +#else + fseeko(m_file, static_cast<off_t>(offset), SEEK_SET); +#endif + + const size_t size = fread(buffer, 1, len, m_file); + + if (size < size_t(len)) + return -1; // error + + return 0; // success +} + +} // namespace mkvparser diff --git a/mkvparser/mkvreader.h b/mkvparser/mkvreader.h new file mode 100644 index 0000000..9831ecf --- /dev/null +++ b/mkvparser/mkvreader.h @@ -0,0 +1,45 @@ +// Copyright (c) 2010 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef MKVPARSER_MKVREADER_H_ +#define MKVPARSER_MKVREADER_H_ + +#include <cstdio> + +#include "mkvparser/mkvparser.h" + +namespace mkvparser { + +class MkvReader : public IMkvReader { + public: + MkvReader(); + explicit MkvReader(FILE* fp); + virtual ~MkvReader(); + + int Open(const char*); + void Close(); + + virtual int Read(long long position, long length, unsigned char* buffer); + virtual int Length(long long* total, long long* available); + + private: + MkvReader(const MkvReader&); + MkvReader& operator=(const MkvReader&); + + // Determines the size of the file. This is called either by the constructor + // or by the Open function depending on file ownership. Returns true on + // success. + bool GetFileSize(); + + long long m_length; + FILE* m_file; + bool reader_owns_file_; +}; + +} // namespace mkvparser + +#endif // MKVPARSER_MKVREADER_H_ diff --git a/mkvparser_sample.cc b/mkvparser_sample.cc new file mode 100644 index 0000000..f285c3e --- /dev/null +++ b/mkvparser_sample.cc @@ -0,0 +1,459 @@ +// Copyright (c) 2010 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// +// This sample application demonstrates how to use the Matroska parser +// library, which allows clients to handle a Matroska format file. +#include <cstdio> +#include <cstdlib> +#include <memory> +#include <new> + +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" + +namespace { +const wchar_t* utf8towcs(const char* str) { + if (str == NULL) + return NULL; + + // TODO: this probably requires that the locale be + // configured somehow: + + const size_t size = mbstowcs(NULL, str, 0); + + if (size == 0 || size == static_cast<size_t>(-1)) + return NULL; + + wchar_t* const val = new (std::nothrow) wchar_t[size + 1]; + if (val == NULL) + return NULL; + + mbstowcs(val, str, size); + val[size] = L'\0'; + + return val; +} + +bool InputHasCues(const mkvparser::Segment* const segment) { + const mkvparser::Cues* const cues = segment->GetCues(); + if (cues == NULL) + return false; + + while (!cues->DoneParsing()) + cues->LoadCuePoint(); + + const mkvparser::CuePoint* const cue_point = cues->GetFirst(); + if (cue_point == NULL) + return false; + + return true; +} + +bool MasteringMetadataValuePresent(double value) { + return value != mkvparser::MasteringMetadata::kValueNotPresent; +} + +bool ColourValuePresent(long long value) { + return value != mkvparser::Colour::kValueNotPresent; +} +} // namespace + +int main(int argc, char* argv[]) { + if (argc == 1) { + printf("Mkv Parser Sample Application\n"); + printf(" Usage: %s <input file> \n", argv[0]); + return EXIT_FAILURE; + } + + mkvparser::MkvReader reader; + + if (reader.Open(argv[1])) { + printf("\n Filename is invalid or error while opening.\n"); + return EXIT_FAILURE; + } + + int maj, min, build, rev; + + mkvparser::GetVersion(maj, min, build, rev); + printf("\t\t libwebm version: %d.%d.%d.%d\n", maj, min, build, rev); + + long long pos = 0; + + mkvparser::EBMLHeader ebmlHeader; + + long long ret = ebmlHeader.Parse(&reader, pos); + if (ret < 0) { + printf("\n EBMLHeader::Parse() failed."); + return EXIT_FAILURE; + } + + printf("\t\t\t EBML Header\n"); + printf("\t\tEBML Version\t\t: %lld\n", ebmlHeader.m_version); + printf("\t\tEBML MaxIDLength\t: %lld\n", ebmlHeader.m_maxIdLength); + printf("\t\tEBML MaxSizeLength\t: %lld\n", ebmlHeader.m_maxSizeLength); + printf("\t\tDoc Type\t\t: %s\n", ebmlHeader.m_docType); + printf("\t\tPos\t\t\t: %lld\n", pos); + + typedef mkvparser::Segment seg_t; + seg_t* pSegment_; + + ret = seg_t::CreateInstance(&reader, pos, pSegment_); + if (ret) { + printf("\n Segment::CreateInstance() failed."); + return EXIT_FAILURE; + } + + const std::unique_ptr<seg_t> pSegment(pSegment_); + + ret = pSegment->Load(); + if (ret < 0) { + printf("\n Segment::Load() failed."); + return EXIT_FAILURE; + } + + const mkvparser::SegmentInfo* const pSegmentInfo = pSegment->GetInfo(); + if (pSegmentInfo == NULL) { + printf("\n Segment::GetInfo() failed."); + return EXIT_FAILURE; + } + + const long long timeCodeScale = pSegmentInfo->GetTimeCodeScale(); + const long long duration_ns = pSegmentInfo->GetDuration(); + + const char* const pTitle_ = pSegmentInfo->GetTitleAsUTF8(); + const wchar_t* const pTitle = utf8towcs(pTitle_); + + const char* const pMuxingApp_ = pSegmentInfo->GetMuxingAppAsUTF8(); + const wchar_t* const pMuxingApp = utf8towcs(pMuxingApp_); + + const char* const pWritingApp_ = pSegmentInfo->GetWritingAppAsUTF8(); + const wchar_t* const pWritingApp = utf8towcs(pWritingApp_); + + printf("\n"); + printf("\t\t\t Segment Info\n"); + printf("\t\tTimeCodeScale\t\t: %lld \n", timeCodeScale); + printf("\t\tDuration\t\t: %lld\n", duration_ns); + + const double duration_sec = double(duration_ns) / 1000000000; + printf("\t\tDuration(secs)\t\t: %7.3lf\n", duration_sec); + + if (pTitle == NULL) + printf("\t\tTrack Name\t\t: NULL\n"); + else { + printf("\t\tTrack Name\t\t: %ls\n", pTitle); + delete[] pTitle; + } + + if (pMuxingApp == NULL) + printf("\t\tMuxing App\t\t: NULL\n"); + else { + printf("\t\tMuxing App\t\t: %ls\n", pMuxingApp); + delete[] pMuxingApp; + } + + if (pWritingApp == NULL) + printf("\t\tWriting App\t\t: NULL\n"); + else { + printf("\t\tWriting App\t\t: %ls\n", pWritingApp); + delete[] pWritingApp; + } + + // pos of segment payload + printf("\t\tPosition(Segment)\t: %lld\n", pSegment->m_start); + + // size of segment payload + printf("\t\tSize(Segment)\t\t: %lld\n", pSegment->m_size); + + const mkvparser::Tracks* pTracks = pSegment->GetTracks(); + + unsigned long track_num = 0; + const unsigned long num_tracks = pTracks->GetTracksCount(); + + printf("\n\t\t\t Track Info\n"); + + while (track_num != num_tracks) { + const mkvparser::Track* const pTrack = + pTracks->GetTrackByIndex(track_num++); + + if (pTrack == NULL) + continue; + + const long trackType = pTrack->GetType(); + const long trackNumber = pTrack->GetNumber(); + const unsigned long long trackUid = pTrack->GetUid(); + const wchar_t* const pTrackName = utf8towcs(pTrack->GetNameAsUTF8()); + + printf("\t\tTrack Type\t\t: %ld\n", trackType); + printf("\t\tTrack Number\t\t: %ld\n", trackNumber); + printf("\t\tTrack Uid\t\t: %lld\n", trackUid); + + if (pTrackName == NULL) + printf("\t\tTrack Name\t\t: NULL\n"); + else { + printf("\t\tTrack Name\t\t: %ls \n", pTrackName); + delete[] pTrackName; + } + + const char* const pCodecId = pTrack->GetCodecId(); + + if (pCodecId == NULL) + printf("\t\tCodec Id\t\t: NULL\n"); + else + printf("\t\tCodec Id\t\t: %s\n", pCodecId); + + size_t codec_private_size = 0; + if (pTrack->GetCodecPrivate(codec_private_size)) { + printf("\t\tCodec private length: %u bytes\n", + static_cast<unsigned int>(codec_private_size)); + } + + const char* const pCodecName_ = pTrack->GetCodecNameAsUTF8(); + const wchar_t* const pCodecName = utf8towcs(pCodecName_); + + if (pCodecName == NULL) + printf("\t\tCodec Name\t\t: NULL\n"); + else { + printf("\t\tCodec Name\t\t: %ls\n", pCodecName); + delete[] pCodecName; + } + + if (trackType == mkvparser::Track::kVideo) { + const mkvparser::VideoTrack* const pVideoTrack = + static_cast<const mkvparser::VideoTrack*>(pTrack); + + const long long width = pVideoTrack->GetWidth(); + printf("\t\tVideo Width\t\t: %lld\n", width); + + const long long height = pVideoTrack->GetHeight(); + printf("\t\tVideo Height\t\t: %lld\n", height); + + const double rate = pVideoTrack->GetFrameRate(); + printf("\t\tVideo Rate\t\t: %f\n", rate); + + const mkvparser::Colour* const colour = pVideoTrack->GetColour(); + if (colour) { + printf("\t\tVideo Colour:\n"); + if (ColourValuePresent(colour->matrix_coefficients)) + printf("\t\t\tMatrixCoefficients: %lld\n", + colour->matrix_coefficients); + if (ColourValuePresent(colour->bits_per_channel)) + printf("\t\t\tBitsPerChannel: %lld\n", colour->bits_per_channel); + if (ColourValuePresent(colour->chroma_subsampling_horz)) + printf("\t\t\tChromaSubsamplingHorz: %lld\n", + colour->chroma_subsampling_horz); + if (ColourValuePresent(colour->chroma_subsampling_vert)) + printf("\t\t\tChromaSubsamplingVert: %lld\n", + colour->chroma_subsampling_vert); + if (ColourValuePresent(colour->cb_subsampling_horz)) + printf("\t\t\tCbSubsamplingHorz: %lld\n", + colour->cb_subsampling_horz); + if (ColourValuePresent(colour->cb_subsampling_vert)) + printf("\t\t\tCbSubsamplingVert: %lld\n", + colour->cb_subsampling_vert); + if (ColourValuePresent(colour->chroma_siting_horz)) + printf("\t\t\tChromaSitingHorz: %lld\n", colour->chroma_siting_horz); + if (ColourValuePresent(colour->chroma_siting_vert)) + printf("\t\t\tChromaSitingVert: %lld\n", colour->chroma_siting_vert); + if (ColourValuePresent(colour->range)) + printf("\t\t\tRange: %lld\n", colour->range); + if (ColourValuePresent(colour->transfer_characteristics)) + printf("\t\t\tTransferCharacteristics: %lld\n", + colour->transfer_characteristics); + if (ColourValuePresent(colour->primaries)) + printf("\t\t\tPrimaries: %lld\n", colour->primaries); + if (ColourValuePresent(colour->max_cll)) + printf("\t\t\tMaxCLL: %lld\n", colour->max_cll); + if (ColourValuePresent(colour->max_fall)) + printf("\t\t\tMaxFALL: %lld\n", colour->max_fall); + if (colour->mastering_metadata) { + const mkvparser::MasteringMetadata* const mm = + colour->mastering_metadata; + printf("\t\t\tMastering Metadata:\n"); + if (MasteringMetadataValuePresent(mm->luminance_max)) + printf("\t\t\t\tLuminanceMax: %f\n", mm->luminance_max); + if (MasteringMetadataValuePresent(mm->luminance_min)) + printf("\t\t\t\tLuminanceMin: %f\n", mm->luminance_min); + if (mm->r) { + printf("\t\t\t\t\tPrimaryRChromaticityX: %f\n", mm->r->x); + printf("\t\t\t\t\tPrimaryRChromaticityY: %f\n", mm->r->y); + } + if (mm->g) { + printf("\t\t\t\t\tPrimaryGChromaticityX: %f\n", mm->g->x); + printf("\t\t\t\t\tPrimaryGChromaticityY: %f\n", mm->g->y); + } + if (mm->b) { + printf("\t\t\t\t\tPrimaryBChromaticityX: %f\n", mm->b->x); + printf("\t\t\t\t\tPrimaryBChromaticityY: %f\n", mm->b->y); + } + if (mm->white_point) { + printf("\t\t\t\t\tWhitePointChromaticityX: %f\n", + mm->white_point->x); + printf("\t\t\t\t\tWhitePointChromaticityY: %f\n", + mm->white_point->y); + } + } + } + + const mkvparser::Projection* const projection = + pVideoTrack->GetProjection(); + if (projection) { + printf("\t\tVideo Projection:\n"); + if (projection->type != mkvparser::Projection::kTypeNotPresent) + printf("\t\t\tProjectionType: %d\n", + static_cast<int>(projection->type)); + if (projection->private_data) { + printf("\t\t\tProjectionPrivate: %u bytes\n", + static_cast<unsigned int>(projection->private_data_length)); + } + if (projection->pose_yaw != mkvparser::Projection::kValueNotPresent) + printf("\t\t\tProjectionPoseYaw: %f\n", projection->pose_yaw); + if (projection->pose_pitch != mkvparser::Projection::kValueNotPresent) + printf("\t\t\tProjectionPosePitch: %f\n", projection->pose_pitch); + if (projection->pose_roll != mkvparser::Projection::kValueNotPresent) + printf("\t\t\tProjectionPosePitch: %f\n", projection->pose_roll); + } + } + + if (trackType == mkvparser::Track::kAudio) { + const mkvparser::AudioTrack* const pAudioTrack = + static_cast<const mkvparser::AudioTrack*>(pTrack); + + const long long channels = pAudioTrack->GetChannels(); + printf("\t\tAudio Channels\t\t: %lld\n", channels); + + const long long bitDepth = pAudioTrack->GetBitDepth(); + printf("\t\tAudio BitDepth\t\t: %lld\n", bitDepth); + + const double sampleRate = pAudioTrack->GetSamplingRate(); + printf("\t\tAddio Sample Rate\t: %.3f\n", sampleRate); + + const long long codecDelay = pAudioTrack->GetCodecDelay(); + printf("\t\tAudio Codec Delay\t\t: %lld\n", codecDelay); + + const long long seekPreRoll = pAudioTrack->GetSeekPreRoll(); + printf("\t\tAudio Seek Pre Roll\t\t: %lld\n", seekPreRoll); + } + } + + printf("\n\n\t\t\t Cluster Info\n"); + const unsigned long clusterCount = pSegment->GetCount(); + + printf("\t\tCluster Count\t: %ld\n\n", clusterCount); + + if (clusterCount == 0) { + printf("\t\tSegment has no clusters.\n"); + return EXIT_FAILURE; + } + + const mkvparser::Cluster* pCluster = pSegment->GetFirst(); + + while (pCluster != NULL && !pCluster->EOS()) { + const long long timeCode = pCluster->GetTimeCode(); + printf("\t\tCluster Time Code\t: %lld\n", timeCode); + + const long long time_ns = pCluster->GetTime(); + printf("\t\tCluster Time (ns)\t: %lld\n", time_ns); + + const mkvparser::BlockEntry* pBlockEntry; + + long status = pCluster->GetFirst(pBlockEntry); + + if (status < 0) // error + { + printf("\t\tError parsing first block of cluster\n"); + fflush(stdout); + return EXIT_FAILURE; + } + + while (pBlockEntry != NULL && !pBlockEntry->EOS()) { + const mkvparser::Block* const pBlock = pBlockEntry->GetBlock(); + const long long trackNum = pBlock->GetTrackNumber(); + const unsigned long tn = static_cast<unsigned long>(trackNum); + const mkvparser::Track* const pTrack = pTracks->GetTrackByNumber(tn); + + if (pTrack == NULL) + printf("\t\t\tBlock\t\t:UNKNOWN TRACK TYPE\n"); + else { + const long long trackType = pTrack->GetType(); + const int frameCount = pBlock->GetFrameCount(); + const long long time_ns = pBlock->GetTime(pCluster); + const long long discard_padding = pBlock->GetDiscardPadding(); + + printf("\t\t\tBlock\t\t:%s,%s,%15lld,%lld\n", + (trackType == mkvparser::Track::kVideo) ? "V" : "A", + pBlock->IsKey() ? "I" : "P", time_ns, discard_padding); + + for (int i = 0; i < frameCount; ++i) { + const mkvparser::Block::Frame& theFrame = pBlock->GetFrame(i); + const long size = theFrame.len; + const long long offset = theFrame.pos; + printf("\t\t\t %15ld,%15llx\n", size, offset); + } + } + + status = pCluster->GetNext(pBlockEntry, pBlockEntry); + + if (status < 0) { + printf("\t\t\tError parsing next block of cluster\n"); + fflush(stdout); + return EXIT_FAILURE; + } + } + + pCluster = pSegment->GetNext(pCluster); + } + + if (InputHasCues(pSegment.get())) { + // Walk them. + const mkvparser::Cues* const cues = pSegment->GetCues(); + const mkvparser::CuePoint* cue = cues->GetFirst(); + int cue_point_num = 1; + + printf("\t\tCues\n"); + do { + for (track_num = 0; track_num < num_tracks; ++track_num) { + const mkvparser::Track* const track = + pTracks->GetTrackByIndex(track_num); + const mkvparser::CuePoint::TrackPosition* const track_pos = + cue->Find(track); + + if (track_pos != NULL) { + const char track_type = + (track->GetType() == mkvparser::Track::kVideo) ? 'V' : 'A'; + printf( + "\t\t\tCue Point %4d Track %3lu(%c) Time %14lld " + "Block %4lld Pos %8llx\n", + cue_point_num, track->GetNumber(), track_type, + cue->GetTime(pSegment.get()), track_pos->m_block, + track_pos->m_pos); + } + } + + cue = cues->GetNext(cue); + ++cue_point_num; + } while (cue != NULL); + } + + const mkvparser::Tags* const tags = pSegment->GetTags(); + if (tags && tags->GetTagCount() > 0) { + printf("\t\tTags\n"); + for (int i = 0; i < tags->GetTagCount(); ++i) { + const mkvparser::Tags::Tag* const tag = tags->GetTag(i); + printf("\t\t\tTag\n"); + for (int j = 0; j < tag->GetSimpleTagCount(); j++) { + const mkvparser::Tags::SimpleTag* const simple_tag = + tag->GetSimpleTag(j); + printf("\t\t\t\tSimple Tag \"%s\" Value \"%s\"\n", + simple_tag->GetTagName(), simple_tag->GetTagString()); + } + } + } + + fflush(stdout); + return EXIT_SUCCESS; +} diff --git a/mkvreader.hpp b/mkvreader.hpp new file mode 100644 index 0000000..d115872 --- /dev/null +++ b/mkvreader.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_MKVREADER_HPP_ +#define LIBWEBM_MKVREADER_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "mkvparser/mkvreader.h" + +#endif // LIBWEBM_MKVREADER_HPP_ diff --git a/mkvwriter.hpp b/mkvwriter.hpp new file mode 100644 index 0000000..9927b3e --- /dev/null +++ b/mkvwriter.hpp @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_MKVWRITER_HPP_ +#define LIBWEBM_MKVWRITER_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "mkvmuxer/mkvwriter.h" + +#endif // LIBWEBM_MKVWRITER_HPP_ diff --git a/sample_muxer_metadata.cc b/sample_muxer_metadata.cc new file mode 100644 index 0000000..6eadcfd --- /dev/null +++ b/sample_muxer_metadata.cc @@ -0,0 +1,391 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// +// This sample application demonstrates how to use the matroska parser +// library, which allows clients to handle a matroska format file. + +#include "sample_muxer_metadata.h" + +#include <cstdio> +#include <string> + +#include "mkvmuxer/mkvmuxer.h" +#include "webvtt/vttreader.h" + +SampleMuxerMetadata::SampleMuxerMetadata() : segment_(NULL) {} + +bool SampleMuxerMetadata::Init(mkvmuxer::Segment* segment) { + if (segment == NULL || segment_ != NULL) + return false; + + segment_ = segment; + return true; +} + +bool SampleMuxerMetadata::Load(const char* file, Kind kind) { + if (kind == kChapters) + return LoadChapters(file); + + uint64_t track_num; + + if (!AddTrack(kind, &track_num)) { + printf("Unable to add track for WebVTT file \"%s\"\n", file); + return false; + } + + return Parse(file, kind, track_num); +} + +bool SampleMuxerMetadata::AddChapters() { + typedef cue_list_t::const_iterator iter_t; + iter_t i = chapter_cues_.begin(); + const iter_t j = chapter_cues_.end(); + + while (i != j) { + const cue_t& chapter = *i++; + + if (!AddChapter(chapter)) + return false; + } + + return true; +} + +bool SampleMuxerMetadata::Write(int64_t time_ns) { + typedef cues_set_t::iterator iter_t; + + iter_t i = cues_set_.begin(); + const iter_t j = cues_set_.end(); + + while (i != j) { + const cues_set_t::value_type& v = *i; + + if (time_ns >= 0 && v > time_ns) + return true; // nothing else to do just yet + + if (!v.Write(segment_)) { + printf("\nCould not add metadata.\n"); + return false; // error + } + + cues_set_.erase(i++); + } + + return true; +} + +bool SampleMuxerMetadata::LoadChapters(const char* file) { + if (!chapter_cues_.empty()) { + printf("Support for more than one chapters file is not yet implemented\n"); + return false; + } + + cue_list_t cues; + + if (!ParseChapters(file, &cues)) + return false; + + // TODO(matthewjheaney): support more than one chapters file + chapter_cues_.swap(cues); + + return true; +} + +bool SampleMuxerMetadata::ParseChapters(const char* file, + cue_list_t* cues_ptr) { + cue_list_t& cues = *cues_ptr; + cues.clear(); + + libwebvtt::VttReader r; + int e = r.Open(file); + + if (e) { + printf("Unable to open WebVTT file: \"%s\"\n", file); + return false; + } + + libwebvtt::Parser p(&r); + e = p.Init(); + + if (e < 0) { // error + printf("Error parsing WebVTT file: \"%s\"\n", file); + return false; + } + + libwebvtt::Time t; + t.hours = -1; + + for (;;) { + cue_t c; + e = p.Parse(&c); + + if (e < 0) { // error + printf("Error parsing WebVTT file: \"%s\"\n", file); + return false; + } + + if (e > 0) // EOF + return true; + + if (c.start_time < t) { + printf("bad WebVTT cue timestamp (out-of-order)\n"); + return false; + } + + if (c.stop_time < c.start_time) { + printf("bad WebVTT cue timestamp (stop < start)\n"); + return false; + } + + t = c.start_time; + cues.push_back(c); + } +} + +bool SampleMuxerMetadata::AddChapter(const cue_t& cue) { + // TODO(matthewjheaney): support language and country + + mkvmuxer::Chapter* const chapter = segment_->AddChapter(); + + if (chapter == NULL) { + printf("Unable to add chapter\n"); + return false; + } + + if (cue.identifier.empty()) { + chapter->set_id(NULL); + } else { + const char* const id = cue.identifier.c_str(); + if (!chapter->set_id(id)) { + printf("Unable to set chapter id\n"); + return false; + } + } + + typedef libwebvtt::presentation_t time_ms_t; + const time_ms_t start_time_ms = cue.start_time.presentation(); + const time_ms_t stop_time_ms = cue.stop_time.presentation(); + + enum { kNsPerMs = 1000000 }; + const uint64_t start_time_ns = start_time_ms * kNsPerMs; + const uint64_t stop_time_ns = stop_time_ms * kNsPerMs; + + chapter->set_time(*segment_, start_time_ns, stop_time_ns); + + typedef libwebvtt::Cue::payload_t::const_iterator iter_t; + iter_t i = cue.payload.begin(); + const iter_t j = cue.payload.end(); + + std::string title; + + for (;;) { + title += *i++; + + if (i == j) + break; + + enum { kLF = '\x0A' }; + title += kLF; + } + + if (!chapter->add_string(title.c_str(), NULL, NULL)) { + printf("Unable to set chapter title\n"); + return false; + } + + return true; +} + +bool SampleMuxerMetadata::AddTrack(Kind kind, uint64_t* track_num) { + *track_num = 0; + + // Track number value 0 means "let muxer choose track number" + mkvmuxer::Track* const track = segment_->AddTrack(0); + + if (track == NULL) // error + return false; + + // Return the track number value chosen by the muxer + *track_num = track->number(); + + int type; + const char* codec_id; + + switch (kind) { + case kSubtitles: + type = 0x11; + codec_id = "D_WEBVTT/SUBTITLES"; + break; + + case kCaptions: + type = 0x11; + codec_id = "D_WEBVTT/CAPTIONS"; + break; + + case kDescriptions: + type = 0x21; + codec_id = "D_WEBVTT/DESCRIPTIONS"; + break; + + case kMetadata: + type = 0x21; + codec_id = "D_WEBVTT/METADATA"; + break; + + default: + return false; + } + + track->set_type(type); + track->set_codec_id(codec_id); + + // TODO(matthewjheaney): set name and language + + return true; +} + +bool SampleMuxerMetadata::Parse(const char* file, Kind /* kind */, + uint64_t track_num) { + libwebvtt::VttReader r; + int e = r.Open(file); + + if (e) { + printf("Unable to open WebVTT file: \"%s\"\n", file); + return false; + } + + libwebvtt::Parser p(&r); + + e = p.Init(); + + if (e < 0) { // error + printf("Error parsing WebVTT file: \"%s\"\n", file); + return false; + } + + SortableCue cue; + cue.track_num = track_num; + + libwebvtt::Time t; + t.hours = -1; + + for (;;) { + cue_t& c = cue.cue; + e = p.Parse(&c); + + if (e < 0) { // error + printf("Error parsing WebVTT file: \"%s\"\n", file); + return false; + } + + if (e > 0) // EOF + return true; + + if (c.start_time >= t) { + t = c.start_time; + } else { + printf("bad WebVTT cue timestamp (out-of-order)\n"); + return false; + } + + if (c.stop_time < c.start_time) { + printf("bad WebVTT cue timestamp (stop < start)\n"); + return false; + } + + cues_set_.insert(cue); + } +} + +void SampleMuxerMetadata::MakeFrame(const cue_t& c, std::string* pf) { + pf->clear(); + WriteCueIdentifier(c.identifier, pf); + WriteCueSettings(c.settings, pf); + WriteCuePayload(c.payload, pf); +} + +void SampleMuxerMetadata::WriteCueIdentifier(const std::string& identifier, + std::string* pf) { + pf->append(identifier); + pf->push_back('\x0A'); // LF +} + +void SampleMuxerMetadata::WriteCueSettings(const cue_t::settings_t& settings, + std::string* pf) { + if (settings.empty()) { + pf->push_back('\x0A'); // LF + return; + } + + typedef cue_t::settings_t::const_iterator iter_t; + + iter_t i = settings.begin(); + const iter_t j = settings.end(); + + for (;;) { + const libwebvtt::Setting& setting = *i++; + + pf->append(setting.name); + pf->push_back(':'); + pf->append(setting.value); + + if (i == j) + break; + + pf->push_back(' '); // separate settings with whitespace + } + + pf->push_back('\x0A'); // LF +} + +void SampleMuxerMetadata::WriteCuePayload(const cue_t::payload_t& payload, + std::string* pf) { + typedef cue_t::payload_t::const_iterator iter_t; + + iter_t i = payload.begin(); + const iter_t j = payload.end(); + + while (i != j) { + const std::string& line = *i++; + pf->append(line); + pf->push_back('\x0A'); // LF + } +} + +bool SampleMuxerMetadata::SortableCue::Write(mkvmuxer::Segment* segment) const { + // Cue start time expressed in milliseconds + const int64_t start_ms = cue.start_time.presentation(); + + // Cue start time expressed in nanoseconds (MKV time) + const int64_t start_ns = start_ms * 1000000; + + // Cue stop time expressed in milliseconds + const int64_t stop_ms = cue.stop_time.presentation(); + + // Cue stop time expressed in nanonseconds + const int64_t stop_ns = stop_ms * 1000000; + + // Metadata blocks always specify the block duration. + const int64_t duration_ns = stop_ns - start_ns; + + std::string frame; + MakeFrame(cue, &frame); + + typedef const uint8_t* data_t; + const data_t buf = reinterpret_cast<data_t>(frame.data()); + const uint64_t len = frame.length(); + + mkvmuxer::Frame muxer_frame; + if (!muxer_frame.Init(buf, len)) + return 0; + muxer_frame.set_track_number(track_num); + muxer_frame.set_timestamp(start_ns); + muxer_frame.set_duration(duration_ns); + muxer_frame.set_is_key(true); // All metadata frames are keyframes. + return segment->AddGenericFrame(&muxer_frame); +}
\ No newline at end of file diff --git a/sample_muxer_metadata.h b/sample_muxer_metadata.h new file mode 100644 index 0000000..d76ccf5 --- /dev/null +++ b/sample_muxer_metadata.h @@ -0,0 +1,137 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef SAMPLE_MUXER_METADATA_H_ // NOLINT +#define SAMPLE_MUXER_METADATA_H_ + +#include <stdint.h> + +#include <list> +#include <set> +#include <string> + +#include "webvtt/webvttparser.h" + +namespace mkvmuxer { +class Chapter; +class Frame; +class Segment; +class Track; +} // namespace mkvmuxer + +class SampleMuxerMetadata { + public: + enum Kind { kSubtitles, kCaptions, kDescriptions, kMetadata, kChapters }; + + SampleMuxerMetadata(); + + // Bind this metadata object to the muxer instance. Returns false + // if segment equals NULL, or Init has already been called. + bool Init(mkvmuxer::Segment* segment); + + // Parse the WebVTT file |filename| having the indicated |kind|, and + // create a corresponding track (or chapters element) in the + // segment. Returns false on error. + bool Load(const char* filename, Kind kind); + + bool AddChapters(); + + // Write any WebVTT cues whose time is less or equal to |time_ns| as + // a metadata block in its corresponding track. If |time_ns| is + // negative, write all remaining cues. Returns false on error. + bool Write(int64_t time_ns); + + private: + typedef libwebvtt::Cue cue_t; + + // Used to sort cues as they are loaded. + struct SortableCue { + bool operator>(int64_t time_ns) const { + // Cue start time expressed in milliseconds + const int64_t start_ms = cue.start_time.presentation(); + + // Cue start time expressed in nanoseconds (MKV time) + const int64_t start_ns = start_ms * 1000000; + + return (start_ns > time_ns); + } + + bool operator<(const SortableCue& rhs) const { + if (cue.start_time < rhs.cue.start_time) + return true; + + if (cue.start_time > rhs.cue.start_time) + return false; + + return (track_num < rhs.track_num); + } + + // Write this cue as a metablock to |segment|. Returns false on + // error. + bool Write(mkvmuxer::Segment* segment) const; + + uint64_t track_num; + cue_t cue; + }; + + typedef std::multiset<SortableCue> cues_set_t; + typedef std::list<cue_t> cue_list_t; + + // Parse the WebVTT cues in the named |file|, returning false on + // error. We handle chapters as a special case, because they are + // stored in their own, dedicated level-1 element. + bool LoadChapters(const char* file); + + // Parse the WebVTT chapters in |file| to populate |cues|. Returns + // false on error. + static bool ParseChapters(const char* file, cue_list_t* cues); + + // Adds WebVTT cue |chapter| to the chapters element of the output + // file's segment element. Returns false on error. + bool AddChapter(const cue_t& chapter); + + // Add a metadata track to the segment having the indicated |kind|, + // returning the |track_num| that has been chosen for this track. + // Returns false on error. + bool AddTrack(Kind kind, uint64_t* track_num); + + // Parse the WebVTT |file| having the indicated |kind| and + // |track_num|, adding each parsed cue to cues set. Returns false + // on error. + bool Parse(const char* file, Kind kind, uint64_t track_num); + + // Converts a WebVTT cue to a Matroska metadata block. + static void MakeFrame(const cue_t& cue, std::string* frame); + + // Populate the cue identifier part of the metadata block. + static void WriteCueIdentifier(const std::string& identifier, + std::string* frame); + + // Populate the cue settings part of the metadata block. + static void WriteCueSettings(const cue_t::settings_t& settings, + std::string* frame); + + // Populate the payload part of the metadata block. + static void WriteCuePayload(const cue_t::payload_t& payload, + std::string* frame); + + mkvmuxer::Segment* segment_; + + // Set of cues ordered by time and then by track number. + cues_set_t cues_set_; + + // The cues that will be used to populate the Chapters level-1 + // element of the output file. + cue_list_t chapter_cues_; + + // Disable copy ctor and copy assign. + SampleMuxerMetadata(const SampleMuxerMetadata&); + SampleMuxerMetadata& operator=(const SampleMuxerMetadata&); +}; + +#endif // SAMPLE_MUXER_METADATA_H_ // NOLINT diff --git a/testing/mkvmuxer_tests.cc b/testing/mkvmuxer_tests.cc new file mode 100644 index 0000000..3374058 --- /dev/null +++ b/testing/mkvmuxer_tests.cc @@ -0,0 +1,1010 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include <stdint.h> + +#include <array> +#include <cstdio> +#include <cstring> +#include <iomanip> +#include <memory> +#include <ostream> +#include <string> + +#include "gtest/gtest.h" + +#include "common/file_util.h" +#include "common/libwebm_util.h" +#include "mkvmuxer/mkvmuxer.h" +#include "mkvmuxer/mkvwriter.h" +#include "mkvparser/mkvreader.h" +#include "testing/test_util.h" + +using mkvmuxer::AudioTrack; +using mkvmuxer::Chapter; +using mkvmuxer::Frame; +using mkvmuxer::MkvWriter; +using mkvmuxer::Segment; +using mkvmuxer::SegmentInfo; +using mkvmuxer::Tag; +using mkvmuxer::Track; +using mkvmuxer::VideoTrack; + +namespace test { + +// Base class containing boiler plate stuff. +class MuxerTest : public testing::Test { + public: + MuxerTest() { Init(); } + + ~MuxerTest() { CloseWriter(); } + + // Simple init function for use by constructor. Calls made here to allow use + // of ASSERT_* macros-- this is necessary here because all failures in Init() + // are fatal, but the ASSERT_* gtest macros cannot be used in a constructor. + void Init() { + ASSERT_TRUE(GetTestDataDir().length() > 0); + filename_ = libwebm::GetTempFileName(); + ASSERT_GT(filename_.length(), 0u); + temp_file_ = libwebm::FilePtr(std::fopen(filename_.c_str(), "wb"), + libwebm::FILEDeleter()); + ASSERT_TRUE(temp_file_.get() != nullptr); + writer_.reset(new MkvWriter(temp_file_.get())); + is_writer_open_ = true; + memset(dummy_data_, 0, kFrameLength); + } + + void AddDummyFrameAndFinalize(int track_number) { + EXPECT_TRUE(segment_.AddFrame(&dummy_data_[0], kFrameLength, track_number, + 0, false)); + EXPECT_TRUE(segment_.Finalize()); + } + + void AddVideoTrack() { + const int vid_track = static_cast<int>( + segment_.AddVideoTrack(kWidth, kHeight, kVideoTrackNumber)); + ASSERT_EQ(kVideoTrackNumber, vid_track); + VideoTrack* const video = + dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(vid_track)); + ASSERT_TRUE(video != NULL); + video->set_uid(kVideoTrackNumber); + } + + void AddAudioTrack() { + const int aud_track = static_cast<int>( + segment_.AddAudioTrack(kSampleRate, kChannels, kAudioTrackNumber)); + ASSERT_EQ(kAudioTrackNumber, aud_track); + AudioTrack* const audio = + dynamic_cast<AudioTrack*>(segment_.GetTrackByNumber(aud_track)); + ASSERT_TRUE(audio != NULL); + audio->set_uid(kAudioTrackNumber); + audio->set_codec_id(kOpusCodecId); + } + + void CloseWriter() { + if (is_writer_open_) + writer_->Close(); + is_writer_open_ = false; + } + + bool SegmentInit(bool output_cues, bool accurate_cluster_duration, + bool fixed_size_cluster_timecode) { + if (!segment_.Init(writer_.get())) + return false; + SegmentInfo* const info = segment_.GetSegmentInfo(); + info->set_writing_app(kAppString); + info->set_muxing_app(kAppString); + segment_.OutputCues(output_cues); + segment_.AccurateClusterDuration(accurate_cluster_duration); + segment_.UseFixedSizeClusterTimecode(fixed_size_cluster_timecode); + return true; + } + + protected: + virtual void TearDown() { + remove(filename_.c_str()); + testing::Test::TearDown(); + } + + std::unique_ptr<MkvWriter> writer_; + bool is_writer_open_ = false; + Segment segment_; + std::string filename_; + libwebm::FilePtr temp_file_; + std::uint8_t dummy_data_[kFrameLength]; +}; + +TEST_F(MuxerTest, SegmentInfo) { + EXPECT_TRUE(SegmentInit(false, false, false)); + SegmentInfo* const info = segment_.GetSegmentInfo(); + info->set_timecode_scale(kTimeCodeScale); + info->set_duration(2.345); + EXPECT_STREQ(kAppString, info->muxing_app()); + EXPECT_STREQ(kAppString, info->writing_app()); + EXPECT_EQ(static_cast<uint64_t>(kTimeCodeScale), info->timecode_scale()); + EXPECT_DOUBLE_EQ(2.345, info->duration()); + AddVideoTrack(); + + AddDummyFrameAndFinalize(kVideoTrackNumber); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("segment_info.webm"), filename_)); +} + +TEST_F(MuxerTest, AddTracks) { + EXPECT_TRUE(SegmentInit(false, false, false)); + + // Add a Video Track + AddVideoTrack(); + VideoTrack* const video = + dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber)); + ASSERT_TRUE(video != NULL); + EXPECT_EQ(static_cast<uint64_t>(kWidth), video->width()); + EXPECT_EQ(static_cast<uint64_t>(kHeight), video->height()); + video->set_name(kTrackName); + video->set_display_width(kWidth - 10); + video->set_display_height(kHeight - 10); + video->set_frame_rate(0.5); + EXPECT_STREQ(kTrackName, video->name()); + const uint64_t kDisplayWidth = kWidth - 10; + EXPECT_EQ(kDisplayWidth, video->display_width()); + const uint64_t kDisplayHeight = kHeight - 10; + EXPECT_EQ(kDisplayHeight, video->display_height()); + EXPECT_DOUBLE_EQ(0.5, video->frame_rate()); + EXPECT_EQ(static_cast<uint64_t>(kVideoTrackNumber), video->uid()); + + // Add an Audio Track + const int aud_track = static_cast<int>( + segment_.AddAudioTrack(kSampleRate, kChannels, kAudioTrackNumber)); + EXPECT_EQ(kAudioTrackNumber, aud_track); + AudioTrack* const audio = + dynamic_cast<AudioTrack*>(segment_.GetTrackByNumber(aud_track)); + EXPECT_EQ(kSampleRate, audio->sample_rate()); + EXPECT_EQ(static_cast<uint64_t>(kChannels), audio->channels()); + ASSERT_TRUE(audio != NULL); + audio->set_name(kTrackName); + audio->set_bit_depth(kBitDepth); + audio->set_uid(kAudioTrackNumber); + EXPECT_STREQ(kTrackName, audio->name()); + EXPECT_EQ(static_cast<uint64_t>(kBitDepth), audio->bit_depth()); + EXPECT_EQ(static_cast<uint64_t>(kAudioTrackNumber), audio->uid()); + + AddDummyFrameAndFinalize(kVideoTrackNumber); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("tracks.webm"), filename_)); +} + +TEST_F(MuxerTest, AddChapters) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + // Add a Chapter + Chapter* chapter = segment_.AddChapter(); + EXPECT_TRUE(chapter->set_id("unit_test")); + chapter->set_time(segment_, 0, 1000000000); + EXPECT_TRUE(chapter->add_string("unit_test", "english", "us")); + chapter->set_uid(1); + + AddDummyFrameAndFinalize(kVideoTrackNumber); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("chapters.webm"), filename_)); +} + +TEST_F(MuxerTest, SimpleBlock) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + // Valid Frame + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, + false)); + + // Valid Frame + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + + // Invalid Frame - Non monotonically increasing timestamp + EXPECT_FALSE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 1, false)); + + // Invalid Frame - Null pointer + EXPECT_FALSE(segment_.AddFrame(NULL, 0, kVideoTrackNumber, 8000000, false)); + + // Invalid Frame - Invalid track number + EXPECT_FALSE(segment_.AddFrame(NULL, 0, kInvalidTrackNumber, 8000000, false)); + + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("simple_block.webm"), filename_)); +} + +TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.set_track_number(kVideoTrackNumber); + frame.set_is_key(false); + + // Valid Frame + frame.set_timestamp(0); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + + // Valid Frame + frame.set_timestamp(2000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + + // Invalid Frame - Non monotonically increasing timestamp + frame.set_timestamp(1); + EXPECT_FALSE(segment_.AddGenericFrame(&frame)); + + // Invalid Frame - Invalid track number + frame.set_track_number(kInvalidTrackNumber); + frame.set_timestamp(8000000); + EXPECT_FALSE(segment_.AddGenericFrame(&frame)); + + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("simple_block.webm"), filename_)); +} + +TEST_F(MuxerTest, MetadataBlock) { + EXPECT_TRUE(SegmentInit(false, false, false)); + Track* const track = segment_.AddTrack(kMetadataTrackNumber); + track->set_uid(kMetadataTrackNumber); + track->set_type(kMetadataTrackType); + track->set_codec_id(kMetadataCodecId); + + // Valid Frame + EXPECT_TRUE(segment_.AddMetadata(dummy_data_, kFrameLength, + kMetadataTrackNumber, 0, 2000000)); + + // Valid Frame + EXPECT_TRUE(segment_.AddMetadata(dummy_data_, kFrameLength, + kMetadataTrackNumber, 2000000, 6000000)); + + // Invalid Frame - Non monotonically increasing timestamp + EXPECT_FALSE(segment_.AddMetadata(dummy_data_, kFrameLength, + kMetadataTrackNumber, 1, 2000000)); + + // Invalid Frame - Null pointer + EXPECT_FALSE(segment_.AddMetadata(NULL, 0, kMetadataTrackNumber, 0, 8000000)); + + // Invalid Frame - Invalid track number + EXPECT_FALSE(segment_.AddMetadata(NULL, 0, kInvalidTrackNumber, 0, 8000000)); + + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("metadata_block.webm"), filename_)); +} + +TEST_F(MuxerTest, TrackType) { + EXPECT_TRUE(SegmentInit(false, false, false)); + Track* const track = segment_.AddTrack(kMetadataTrackNumber); + track->set_uid(kMetadataTrackNumber); + track->set_codec_id(kMetadataCodecId); + + // Invalid Frame - Incomplete track information (Track Type not set). + EXPECT_FALSE(segment_.AddMetadata(dummy_data_, kFrameLength, + kMetadataTrackNumber, 0, 2000000)); + + track->set_type(kMetadataTrackType); + + // Valid Frame + EXPECT_TRUE(segment_.AddMetadata(dummy_data_, kFrameLength, + kMetadataTrackNumber, 0, 2000000)); + + segment_.Finalize(); + CloseWriter(); +} + +TEST_F(MuxerTest, BlockWithAdditional) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + // Valid Frame + EXPECT_TRUE(segment_.AddFrameWithAdditional(dummy_data_, kFrameLength, + dummy_data_, kFrameLength, 1, + kVideoTrackNumber, 0, true)); + + // Valid Frame + EXPECT_TRUE(segment_.AddFrameWithAdditional( + dummy_data_, kFrameLength, dummy_data_, kFrameLength, 1, + kVideoTrackNumber, 2000000, false)); + + // Invalid Frame - Non monotonically increasing timestamp + EXPECT_FALSE(segment_.AddFrameWithAdditional(dummy_data_, kFrameLength, + dummy_data_, kFrameLength, 1, + kVideoTrackNumber, 1, false)); + + // Invalid Frame - Null frame pointer + EXPECT_FALSE( + segment_.AddFrameWithAdditional(NULL, 0, dummy_data_, kFrameLength, 1, + kVideoTrackNumber, 3000000, false)); + + // Invalid Frame - Null additional pointer + EXPECT_FALSE(segment_.AddFrameWithAdditional(dummy_data_, kFrameLength, NULL, + 0, 1, kVideoTrackNumber, 4000000, + false)); + + // Invalid Frame - Invalid track number + EXPECT_FALSE(segment_.AddFrameWithAdditional( + dummy_data_, kFrameLength, dummy_data_, kFrameLength, 1, + kInvalidTrackNumber, 8000000, false)); + + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("block_with_additional.webm"), filename_)); +} + +TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.AddAdditionalData(dummy_data_, kFrameLength, 1); + frame.set_track_number(kVideoTrackNumber); + frame.set_is_key(true); + + // Valid Frame + frame.set_timestamp(0); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + + // Valid Frame + frame.set_timestamp(2000000); + frame.set_is_key(false); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + + // Invalid Frame - Non monotonically increasing timestamp + frame.set_timestamp(1); + EXPECT_FALSE(segment_.AddGenericFrame(&frame)); + + // Invalid Frame - Invalid track number + frame.set_track_number(kInvalidTrackNumber); + frame.set_timestamp(4000000); + EXPECT_FALSE(segment_.AddGenericFrame(&frame)); + + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("block_with_additional.webm"), filename_)); +} + +TEST_F(MuxerTest, SegmentDurationComputation) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.set_track_number(kVideoTrackNumber); + frame.set_timestamp(0); + frame.set_is_key(false); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + frame.set_timestamp(2000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + frame.set_timestamp(4000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + frame.set_timestamp(6000000); + frame.set_duration(2000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.Finalize(); + + // SegmentInfo's duration is in timecode scale + EXPECT_EQ(8, segment_.GetSegmentInfo()->duration()); + + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("segment_duration.webm"), filename_)); +} + +TEST_F(MuxerTest, SetSegmentDuration) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, + false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + + segment_.set_duration(10500.0); + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("set_segment_duration.webm"), filename_)); +} + +TEST_F(MuxerTest, ForceNewCluster) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, + false)); + segment_.ForceNewClusterOnNextFrame(); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 4000000, false)); + segment_.ForceNewClusterOnNextFrame(); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 6000000, false)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("force_new_cluster.webm"), filename_)); +} + +TEST_F(MuxerTest, OutputCues) { + EXPECT_TRUE(SegmentInit(true, false, false)); + AddVideoTrack(); + + EXPECT_TRUE( + segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, true)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 4000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 6000000, true)); + EXPECT_TRUE(segment_.AddCuePoint(4000000, kVideoTrackNumber)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("output_cues.webm"), filename_)); +} + +TEST_F(MuxerTest, CuesBeforeClusters) { + EXPECT_TRUE(SegmentInit(true, false, false)); + AddVideoTrack(); + + EXPECT_TRUE( + segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, true)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 4000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 6000000, true)); + segment_.Finalize(); + CloseWriter(); +#ifdef _MSC_VER + // Close the output file: the MS run time won't allow mkvparser::MkvReader + // to open a file for reading when it's still open for writing. + temp_file_.reset(); +#endif + mkvparser::MkvReader reader; + ASSERT_EQ(0, reader.Open(filename_.c_str())); + MkvWriter cues_writer; + std::string cues_filename = libwebm::GetTempFileName(); + ASSERT_GT(cues_filename.length(), 0u); + cues_writer.Open(cues_filename.c_str()); + EXPECT_TRUE(segment_.CopyAndMoveCuesBeforeClusters(&reader, &cues_writer)); + reader.Close(); + cues_writer.Close(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("cues_before_clusters.webm"), + cues_filename)); + MkvParser parser; + ASSERT_TRUE(ParseMkvFileReleaseParser(cues_filename, &parser)); + int64_t cues_offset = 0; + ASSERT_TRUE(HasCuePoints(parser.segment, &cues_offset)); + ASSERT_GT(cues_offset, 0); + ASSERT_TRUE(ValidateCues(parser.segment, parser.reader)); + remove(cues_filename.c_str()); +} + +TEST_F(MuxerTest, MaxClusterSize) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + const uint64_t kMaxClusterSize = 20; + segment_.set_max_cluster_size(kMaxClusterSize); + EXPECT_EQ(kMaxClusterSize, segment_.max_cluster_size()); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, 1, kVideoTrackNumber, 0, false)); + EXPECT_TRUE( + segment_.AddFrame(dummy_data_, 1, kVideoTrackNumber, 2000000, false)); + EXPECT_TRUE( + segment_.AddFrame(dummy_data_, 1, kVideoTrackNumber, 4000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 6000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 8000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 9000000, false)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("max_cluster_size.webm"), filename_)); +} + +TEST_F(MuxerTest, MaxClusterDuration) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + const uint64_t kMaxClusterDuration = 4000000; + segment_.set_max_cluster_duration(kMaxClusterDuration); + + EXPECT_EQ(kMaxClusterDuration, segment_.max_cluster_duration()); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, + false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 4000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 6000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 8000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 9000000, false)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("max_cluster_duration.webm"), filename_)); +} + +TEST_F(MuxerTest, SetCuesTrackNumber) { + const uint64_t kTrackNumber = 10; + EXPECT_TRUE(SegmentInit(true, false, false)); + const uint64_t vid_track = + segment_.AddVideoTrack(kWidth, kHeight, kTrackNumber); + EXPECT_EQ(kTrackNumber, vid_track); + segment_.GetTrackByNumber(vid_track)->set_uid(kVideoTrackNumber); + EXPECT_TRUE(segment_.CuesTrack(vid_track)); + + EXPECT_EQ(vid_track, segment_.cues_track()); + EXPECT_TRUE( + segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber, 0, true)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber, + 2000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber, + 4000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber, + 6000000, true)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber, + 8000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber, + 9000000, false)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("set_cues_track_number.webm"), filename_)); +} + +TEST_F(MuxerTest, BlockWithDiscardPadding) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddAudioTrack(); + + int timecode = 1000; + // 12810000 == 0xc37710, should be 0-extended to avoid changing the sign. + // The next two should be written as 1 byte. + std::array<int, 3> values = {{12810000, 127, -128}}; + for (const std::int64_t discard_padding : values) { + EXPECT_TRUE(segment_.AddFrameWithDiscardPadding( + dummy_data_, kFrameLength, discard_padding, kAudioTrackNumber, timecode, + true)) + << "discard_padding: " << discard_padding; + timecode += 1000; + } + + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("discard_padding.webm"), filename_)); +} + +TEST_F(MuxerTest, AccurateClusterDuration) { + EXPECT_TRUE(SegmentInit(false, true, false)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.set_track_number(kVideoTrackNumber); + frame.set_timestamp(0); + frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(2000000); + frame.set_is_key(false); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + frame.set_timestamp(4000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(6000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("accurate_cluster_duration.webm"), + filename_)); +} + +// Tests AccurateClusterDuration flag with the duration of the very last block +// of the file set explicitly. +TEST_F(MuxerTest, AccurateClusterDurationExplicitLastFrameDuration) { + EXPECT_TRUE(SegmentInit(false, true, false)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.set_track_number(kVideoTrackNumber); + frame.set_timestamp(0); + frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(2000000); + frame.set_is_key(false); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + frame.set_timestamp(4000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(6000000); + frame.set_duration(2000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.Finalize(); + + // SegmentInfo's duration is in timecode scale + EXPECT_EQ(8, segment_.GetSegmentInfo()->duration()); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles( + GetTestFilePath("accurate_cluster_duration_last_frame.webm"), filename_)); +} + +TEST_F(MuxerTest, AccurateClusterDurationTwoTracks) { + EXPECT_TRUE(SegmentInit(false, true, false)); + AddVideoTrack(); + AddAudioTrack(); + + Frame video_frame; + video_frame.Init(dummy_data_, kFrameLength); + video_frame.set_track_number(kVideoTrackNumber); + Frame audio_frame; + audio_frame.Init(dummy_data_, kFrameLength); + audio_frame.set_track_number(kAudioTrackNumber); + std::array<std::uint64_t, 2> cluster_timestamps = {{0, 40000000}}; + for (const std::uint64_t cluster_timestamp : cluster_timestamps) { + // Add video and audio frames with timestamp 0. + video_frame.set_timestamp(cluster_timestamp); + video_frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&video_frame)); + audio_frame.set_timestamp(cluster_timestamp); + audio_frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&audio_frame)); + + // Add 3 consecutive audio frames. + std::array<std::uint64_t, 3> audio_timestamps = { + {10000000, 20000000, 30000000}}; + for (const std::uint64_t audio_timestamp : audio_timestamps) { + audio_frame.set_timestamp(cluster_timestamp + audio_timestamp); + // Explicitly set duration for the very last audio frame. + if (cluster_timestamp == 40000000 && audio_timestamp == 30000000) { + audio_frame.set_duration(10000000); + } + EXPECT_TRUE(segment_.AddGenericFrame(&audio_frame)); + } + + // Add a video frame with timestamp 33ms. + video_frame.set_is_key(false); + // Explicitly set duration for the very last video frame. + if (cluster_timestamp == 40000000) { + video_frame.set_duration(7000000); + } + video_frame.set_timestamp(cluster_timestamp + 33000000); + EXPECT_TRUE(segment_.AddGenericFrame(&video_frame)); + segment_.ForceNewClusterOnNextFrame(); + } + segment_.Finalize(); + + // SegmentInfo's duration is in timecode scale + EXPECT_EQ(80, segment_.GetSegmentInfo()->duration()); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles( + GetTestFilePath("accurate_cluster_duration_two_tracks.webm"), filename_)); +} + +TEST_F(MuxerTest, AccurateClusterDurationWithoutFinalizingCluster) { + EXPECT_TRUE(SegmentInit(false, true, false)); + AddVideoTrack(); + + // Add a couple of frames and then bail out without finalizing the Segment + // (and thereby not finalizing the Cluster). The expectation here is that + // there shouldn't be any leaks. The test will fail under valgrind if there's + // a leak. + Frame video_frame; + video_frame.Init(dummy_data_, kFrameLength); + video_frame.set_track_number(kVideoTrackNumber); + video_frame.set_timestamp(0); + video_frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&video_frame)); + video_frame.set_timestamp(33000000); + EXPECT_TRUE(segment_.AddGenericFrame(&video_frame)); + + CloseWriter(); +} + +TEST_F(MuxerTest, UseFixedSizeClusterTimecode) { + EXPECT_TRUE(SegmentInit(false, false, true)); + AddVideoTrack(); + + Frame frame; + frame.Init(dummy_data_, kFrameLength); + frame.set_track_number(kVideoTrackNumber); + frame.set_timestamp(0); + frame.set_is_key(true); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(2000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.ForceNewClusterOnNextFrame(); + frame.set_timestamp(4000000); + EXPECT_TRUE(segment_.AddGenericFrame(&frame)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("fixed_size_cluster_timecode.webm"), + filename_)); +} + +TEST_F(MuxerTest, DocTypeWebm) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + Track* const vid_track = segment_.GetTrackByNumber(kVideoTrackNumber); + vid_track->set_codec_id(kVP9CodecId); + AddDummyFrameAndFinalize(kVideoTrackNumber); + EXPECT_TRUE(CompareFiles(GetTestFilePath("webm_doctype.webm"), filename_)); +} + +TEST_F(MuxerTest, DocTypeMatroska) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + Track* const vid_track = segment_.GetTrackByNumber(kVideoTrackNumber); + vid_track->set_codec_id("V_SOMETHING_NOT_IN_WEBM"); + AddDummyFrameAndFinalize(kVideoTrackNumber); + EXPECT_TRUE(CompareFiles(GetTestFilePath("matroska_doctype.mkv"), filename_)); +} + +TEST_F(MuxerTest, Colour) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + mkvmuxer::PrimaryChromaticity muxer_pc(.1, .2); + mkvmuxer::MasteringMetadata muxer_mm; + muxer_mm.set_luminance_min(30.0); + muxer_mm.set_luminance_max(40.0); + ASSERT_TRUE( + muxer_mm.SetChromaticity(&muxer_pc, &muxer_pc, &muxer_pc, &muxer_pc)); + + mkvmuxer::Colour muxer_colour; + muxer_colour.set_matrix_coefficients(mkvmuxer::Colour::kGbr); + muxer_colour.set_bits_per_channel(1); + muxer_colour.set_chroma_subsampling_horz(2); + muxer_colour.set_chroma_subsampling_vert(3); + muxer_colour.set_cb_subsampling_horz(4); + muxer_colour.set_cb_subsampling_vert(5); + muxer_colour.set_chroma_siting_horz(mkvmuxer::Colour::kLeftCollocated); + muxer_colour.set_chroma_siting_vert(mkvmuxer::Colour::kTopCollocated); + muxer_colour.set_range(mkvmuxer::Colour::kFullRange); + muxer_colour.set_transfer_characteristics(mkvmuxer::Colour::kLog); + muxer_colour.set_primaries(mkvmuxer::Colour::kSmpteSt4281P); + muxer_colour.set_max_cll(11); + muxer_colour.set_max_fall(12); + ASSERT_TRUE(muxer_colour.SetMasteringMetadata(muxer_mm)); + + VideoTrack* const video_track = + dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber)); + ASSERT_TRUE(video_track != nullptr); + ASSERT_TRUE(video_track->SetColour(muxer_colour)); + ASSERT_NO_FATAL_FAILURE(AddDummyFrameAndFinalize(kVideoTrackNumber)); + + MkvParser parser; + ASSERT_TRUE(ParseMkvFileReleaseParser(filename_, &parser)); + + const mkvparser::VideoTrack* const parser_track = + static_cast<const mkvparser::VideoTrack*>( + parser.segment->GetTracks()->GetTrackByIndex(0)); + const mkvparser::Colour* parser_colour = parser_track->GetColour(); + ASSERT_TRUE(parser_colour != nullptr); + EXPECT_EQ(static_cast<long long>(muxer_colour.matrix_coefficients()), + parser_colour->matrix_coefficients); + EXPECT_EQ(static_cast<long long>(muxer_colour.bits_per_channel()), + parser_colour->bits_per_channel); + EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_subsampling_horz()), + parser_colour->chroma_subsampling_horz); + EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_subsampling_vert()), + parser_colour->chroma_subsampling_vert); + EXPECT_EQ(static_cast<long long>(muxer_colour.cb_subsampling_horz()), + parser_colour->cb_subsampling_horz); + EXPECT_EQ(static_cast<long long>(muxer_colour.cb_subsampling_vert()), + parser_colour->cb_subsampling_vert); + EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_siting_horz()), + parser_colour->chroma_siting_horz); + EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_siting_vert()), + parser_colour->chroma_siting_vert); + EXPECT_EQ(static_cast<long long>(muxer_colour.range()), parser_colour->range); + EXPECT_EQ(static_cast<long long>(muxer_colour.transfer_characteristics()), + parser_colour->transfer_characteristics); + EXPECT_EQ(static_cast<long long>(muxer_colour.primaries()), + parser_colour->primaries); + EXPECT_EQ(static_cast<long long>(muxer_colour.max_cll()), + parser_colour->max_cll); + EXPECT_EQ(static_cast<long long>(muxer_colour.max_fall()), + parser_colour->max_fall); + + const mkvparser::MasteringMetadata* const parser_mm = + parser_colour->mastering_metadata; + ASSERT_TRUE(parser_mm != nullptr); + EXPECT_FLOAT_EQ(muxer_mm.luminance_min(), parser_mm->luminance_min); + EXPECT_FLOAT_EQ(muxer_mm.luminance_max(), parser_mm->luminance_max); + EXPECT_FLOAT_EQ(muxer_mm.r()->x(), parser_mm->r->x); + EXPECT_FLOAT_EQ(muxer_mm.r()->y(), parser_mm->r->y); + EXPECT_FLOAT_EQ(muxer_mm.g()->x(), parser_mm->g->x); + EXPECT_FLOAT_EQ(muxer_mm.g()->y(), parser_mm->g->y); + EXPECT_FLOAT_EQ(muxer_mm.b()->x(), parser_mm->b->x); + EXPECT_FLOAT_EQ(muxer_mm.b()->y(), parser_mm->b->y); + EXPECT_FLOAT_EQ(muxer_mm.white_point()->x(), parser_mm->white_point->x); + EXPECT_FLOAT_EQ(muxer_mm.white_point()->y(), parser_mm->white_point->y); + EXPECT_TRUE(CompareFiles(GetTestFilePath("colour.webm"), filename_)); +} + +TEST_F(MuxerTest, ColourPartial) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + mkvmuxer::Colour muxer_colour; + muxer_colour.set_matrix_coefficients( + mkvmuxer::Colour::kBt2020NonConstantLuminance); + + VideoTrack* const video_track = + dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber)); + ASSERT_TRUE(video_track != nullptr); + ASSERT_TRUE(video_track->SetColour(muxer_colour)); + ASSERT_NO_FATAL_FAILURE(AddDummyFrameAndFinalize(kVideoTrackNumber)); + + MkvParser parser; + ASSERT_TRUE(ParseMkvFileReleaseParser(filename_, &parser)); + + const mkvparser::VideoTrack* const parser_track = + static_cast<const mkvparser::VideoTrack*>( + parser.segment->GetTracks()->GetTrackByIndex(0)); + const mkvparser::Colour* parser_colour = parser_track->GetColour(); + EXPECT_EQ(static_cast<long long>(muxer_colour.matrix_coefficients()), + parser_colour->matrix_coefficients); +} + +TEST_F(MuxerTest, Projection) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + + mkvmuxer::Projection muxer_proj; + muxer_proj.set_type(mkvmuxer::Projection::kRectangular); + muxer_proj.set_pose_yaw(1); + muxer_proj.set_pose_pitch(2); + muxer_proj.set_pose_roll(3); + const uint8_t muxer_proj_private[1] = {4}; + const uint64_t muxer_proj_private_length = 1; + ASSERT_TRUE(muxer_proj.SetProjectionPrivate(&muxer_proj_private[0], + muxer_proj_private_length)); + + VideoTrack* const video_track = + static_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber)); + ASSERT_TRUE(video_track != nullptr); + ASSERT_TRUE(video_track->SetProjection(muxer_proj)); + ASSERT_NO_FATAL_FAILURE(AddDummyFrameAndFinalize(kVideoTrackNumber)); + + MkvParser parser; + ASSERT_TRUE(ParseMkvFileReleaseParser(filename_, &parser)); + + const mkvparser::VideoTrack* const parser_track = + static_cast<const mkvparser::VideoTrack*>( + parser.segment->GetTracks()->GetTrackByIndex(0)); + + const mkvparser::Projection* const parser_proj = + parser_track->GetProjection(); + ASSERT_TRUE(parser_proj != nullptr); + EXPECT_FLOAT_EQ(muxer_proj.pose_yaw(), parser_proj->pose_yaw); + EXPECT_FLOAT_EQ(muxer_proj.pose_pitch(), parser_proj->pose_pitch); + EXPECT_FLOAT_EQ(muxer_proj.pose_roll(), parser_proj->pose_roll); + ASSERT_TRUE(parser_proj->private_data != nullptr); + EXPECT_EQ(static_cast<size_t>(muxer_proj.private_data_length()), + parser_proj->private_data_length); + + EXPECT_EQ(muxer_proj.private_data()[0], parser_proj->private_data[0]); + typedef mkvparser::Projection::ProjectionType ParserProjType; + EXPECT_EQ(static_cast<ParserProjType>(muxer_proj.type()), parser_proj->type); + EXPECT_TRUE(CompareFiles(GetTestFilePath("projection.webm"), filename_)); +} + +TEST_F(MuxerTest, EstimateDuration) { + EXPECT_TRUE(SegmentInit(false, false, false)); + segment_.set_estimate_file_duration(true); + AddVideoTrack(); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, + false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 4000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 6000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 8000000, false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 9000000, false)); + segment_.Finalize(); + + CloseWriter(); + + EXPECT_TRUE( + CompareFiles(GetTestFilePath("estimate_duration.webm"), filename_)); +} + +TEST_F(MuxerTest, SetPixelWidthPixelHeight) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + VideoTrack* const video_track = + static_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber)); + ASSERT_TRUE(video_track != nullptr); + video_track->set_pixel_width(500); + video_track->set_pixel_height(650); + + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, + false)); + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, + 2000000, false)); + + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("set_pixelwidth_pixelheight.webm"), + filename_)); +} + +TEST_F(MuxerTest, LongTagString) { + EXPECT_TRUE(SegmentInit(false, false, false)); + AddVideoTrack(); + Tag* const tag = segment_.AddTag(); + // 160 needs two bytes when varint encoded. + const std::string dummy_string(160, '0'); + tag->add_simple_tag("long_tag", dummy_string.c_str()); + + EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, + false)); + + segment_.Finalize(); + CloseWriter(); + + EXPECT_TRUE(CompareFiles(GetTestFilePath("long_tag_string.webm"), filename_)); +} + +} // namespace test + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/testing/mkvparser_tests.cc b/testing/mkvparser_tests.cc new file mode 100644 index 0000000..9c26a1e --- /dev/null +++ b/testing/mkvparser_tests.cc @@ -0,0 +1,823 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "gtest/gtest.h" + +#include <array> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <iomanip> +#include <string> + +#include "common/hdr_util.h" +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" +#include "testing/test_util.h" + +using mkvparser::AudioTrack; +using mkvparser::Block; +using mkvparser::BlockEntry; +using mkvparser::BlockGroup; +using mkvparser::Cluster; +using mkvparser::CuePoint; +using mkvparser::Cues; +using mkvparser::MkvReader; +using mkvparser::Segment; +using mkvparser::SegmentInfo; +using mkvparser::Track; +using mkvparser::Tracks; +using mkvparser::VideoTrack; + +namespace test { + +// Base class containing boiler plate stuff. +class ParserTest : public testing::Test { + public: + ParserTest() : is_reader_open_(false), segment_(NULL) { + memset(dummy_data_, -1, kFrameLength); + memset(gold_frame_, 0, kFrameLength); + } + + virtual ~ParserTest() { + CloseReader(); + if (segment_ != NULL) { + delete segment_; + segment_ = NULL; + } + } + + void CloseReader() { + if (is_reader_open_) { + reader_.Close(); + } + is_reader_open_ = false; + } + + bool CreateAndLoadSegment(const std::string& filename, + int expected_doc_type_ver) { + filename_ = GetTestFilePath(filename); + if (reader_.Open(filename_.c_str())) { + return false; + } + is_reader_open_ = true; + pos_ = 0; + mkvparser::EBMLHeader ebml_header; + ebml_header.Parse(&reader_, pos_); + EXPECT_EQ(1, ebml_header.m_version); + EXPECT_EQ(1, ebml_header.m_readVersion); + EXPECT_STREQ("webm", ebml_header.m_docType); + EXPECT_EQ(expected_doc_type_ver, ebml_header.m_docTypeVersion); + EXPECT_EQ(2, ebml_header.m_docTypeReadVersion); + + if (mkvparser::Segment::CreateInstance(&reader_, pos_, segment_)) { + return false; + } + return !HasFailure() && segment_->Load() >= 0; + } + + bool CreateAndLoadSegment(const std::string& filename) { + return CreateAndLoadSegment(filename, 4); + } + + void CreateSegmentNoHeaderChecks(const std::string& filename) { + filename_ = GetTestFilePath(filename); + ASSERT_NE(0u, filename_.length()); + ASSERT_EQ(0, reader_.Open(filename_.c_str())); + mkvparser::EBMLHeader ebml_header; + ASSERT_EQ(0, ebml_header.Parse(&reader_, pos_)); + ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, segment_)); + } + + void CompareBlockContents(const Cluster* const cluster, + const Block* const block, std::uint64_t timestamp, + int track_number, bool is_key, int frame_count) { + ASSERT_TRUE(block != NULL); + EXPECT_EQ(track_number, block->GetTrackNumber()); + EXPECT_EQ(static_cast<long long>(timestamp), block->GetTime(cluster)); + EXPECT_EQ(is_key, block->IsKey()); + EXPECT_EQ(frame_count, block->GetFrameCount()); + const Block::Frame& frame = block->GetFrame(0); + EXPECT_EQ(kFrameLength, frame.len); + std::memset(dummy_data_, -1, kFrameLength); + frame.Read(&reader_, dummy_data_); + EXPECT_EQ(0, std::memcmp(gold_frame_, dummy_data_, kFrameLength)); + } + + void CompareCuePointContents(const Track* const track, + const CuePoint* const cue_point, + std::uint64_t timestamp, int track_number, + std::uint64_t pos) { + ASSERT_TRUE(cue_point != NULL); + EXPECT_EQ(static_cast<long long>(timestamp), cue_point->GetTime(segment_)); + const CuePoint::TrackPosition* const track_position = + cue_point->Find(track); + EXPECT_EQ(track_number, track_position->m_track); + EXPECT_EQ(static_cast<long long>(pos), track_position->m_pos); + } + + protected: + MkvReader reader_; + bool is_reader_open_; + Segment* segment_; + std::string filename_; + long long pos_; + std::uint8_t dummy_data_[kFrameLength]; + std::uint8_t gold_frame_[kFrameLength]; +}; + +TEST_F(ParserTest, SegmentInfo) { + ASSERT_TRUE(CreateAndLoadSegment("segment_info.webm")); + const SegmentInfo* const info = segment_->GetInfo(); + EXPECT_EQ(kTimeCodeScale, info->GetTimeCodeScale()); + EXPECT_STREQ(kAppString, info->GetMuxingAppAsUTF8()); + EXPECT_STREQ(kAppString, info->GetWritingAppAsUTF8()); +} + +TEST_F(ParserTest, TrackEntries) { + ASSERT_TRUE(CreateAndLoadSegment("tracks.webm")); + const Tracks* const tracks = segment_->GetTracks(); + const unsigned int kTracksCount = 2; + EXPECT_EQ(kTracksCount, tracks->GetTracksCount()); + for (int i = 0; i < 2; ++i) { + const Track* const track = tracks->GetTrackByIndex(i); + ASSERT_TRUE(track != NULL); + EXPECT_STREQ(kTrackName, track->GetNameAsUTF8()); + if (track->GetType() == Track::kVideo) { + const VideoTrack* const video_track = + dynamic_cast<const VideoTrack*>(track); + EXPECT_EQ(kWidth, static_cast<int>(video_track->GetWidth())); + EXPECT_EQ(kHeight, static_cast<int>(video_track->GetHeight())); + EXPECT_STREQ(kVP8CodecId, video_track->GetCodecId()); + EXPECT_DOUBLE_EQ(kVideoFrameRate, video_track->GetFrameRate()); + const unsigned int kTrackUid = 1; + EXPECT_EQ(kTrackUid, video_track->GetUid()); + } else if (track->GetType() == Track::kAudio) { + const AudioTrack* const audio_track = + dynamic_cast<const AudioTrack*>(track); + EXPECT_EQ(kSampleRate, audio_track->GetSamplingRate()); + EXPECT_EQ(kChannels, audio_track->GetChannels()); + EXPECT_EQ(kBitDepth, audio_track->GetBitDepth()); + EXPECT_STREQ(kVorbisCodecId, audio_track->GetCodecId()); + const unsigned int kTrackUid = 2; + EXPECT_EQ(kTrackUid, audio_track->GetUid()); + } + } +} + +TEST_F(ParserTest, SimpleBlock) { + ASSERT_TRUE(CreateAndLoadSegment("simple_block.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + // Get the cluster + const Cluster* cluster = segment_->GetFirst(); + ASSERT_TRUE(cluster != NULL); + EXPECT_FALSE(cluster->EOS()); + + // Get the first block + const BlockEntry* block_entry; + EXPECT_EQ(0, cluster->GetFirst(block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + CompareBlockContents(cluster, block_entry->GetBlock(), 0, kVideoTrackNumber, + false, 1); + + // Get the second block + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + CompareBlockContents(cluster, block_entry->GetBlock(), 2000000, + kVideoTrackNumber, false, 1); + + // End of Stream + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + ASSERT_EQ(NULL, block_entry); + cluster = segment_->GetNext(cluster); + EXPECT_TRUE(cluster->EOS()); +} + +TEST_F(ParserTest, MultipleClusters) { + ASSERT_TRUE(CreateAndLoadSegment("force_new_cluster.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + // Get the first cluster + const Cluster* cluster = segment_->GetFirst(); + ASSERT_TRUE(cluster != NULL); + EXPECT_FALSE(cluster->EOS()); + + // Get the first block + const BlockEntry* block_entry; + EXPECT_EQ(0, cluster->GetFirst(block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + CompareBlockContents(cluster, block_entry->GetBlock(), 0, kVideoTrackNumber, + false, 1); + + // Get the second cluster + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + EXPECT_EQ(NULL, block_entry); + cluster = segment_->GetNext(cluster); + ASSERT_TRUE(cluster != NULL); + EXPECT_FALSE(cluster->EOS()); + + // Get the second block + EXPECT_EQ(0, cluster->GetFirst(block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + CompareBlockContents(cluster, block_entry->GetBlock(), 2000000, + kVideoTrackNumber, false, 1); + + // Get the third block + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + CompareBlockContents(cluster, block_entry->GetBlock(), 4000000, + kVideoTrackNumber, false, 1); + + // Get the third cluster + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + EXPECT_EQ(NULL, block_entry); + cluster = segment_->GetNext(cluster); + ASSERT_TRUE(cluster != NULL); + EXPECT_FALSE(cluster->EOS()); + + // Get the fourth block + EXPECT_EQ(0, cluster->GetFirst(block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + CompareBlockContents(cluster, block_entry->GetBlock(), 6000000, + kVideoTrackNumber, false, 1); + + // End of Stream + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + EXPECT_EQ(NULL, block_entry); + cluster = segment_->GetNext(cluster); + EXPECT_TRUE(cluster->EOS()); +} + +TEST_F(ParserTest, BlockGroup) { + ASSERT_TRUE(CreateAndLoadSegment("metadata_block.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + // Get the cluster + const Cluster* cluster = segment_->GetFirst(); + ASSERT_TRUE(cluster != NULL); + EXPECT_FALSE(cluster->EOS()); + + // Get the first block + const BlockEntry* block_entry; + EXPECT_EQ(0, cluster->GetFirst(block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + EXPECT_EQ(BlockEntry::Kind::kBlockGroup, block_entry->GetKind()); + const BlockGroup* block_group = static_cast<const BlockGroup*>(block_entry); + EXPECT_EQ(2, block_group->GetDurationTimeCode()); + CompareBlockContents(cluster, block_group->GetBlock(), 0, + kMetadataTrackNumber, true, 1); + + // Get the second block + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + EXPECT_EQ(BlockEntry::Kind::kBlockGroup, block_entry->GetKind()); + block_group = static_cast<const BlockGroup*>(block_entry); + EXPECT_EQ(6, block_group->GetDurationTimeCode()); + CompareBlockContents(cluster, block_group->GetBlock(), 2000000, + kMetadataTrackNumber, true, 1); + + // End of Stream + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + EXPECT_EQ(NULL, block_entry); + cluster = segment_->GetNext(cluster); + EXPECT_TRUE(cluster->EOS()); +} + +TEST_F(ParserTest, Cues) { + ASSERT_TRUE(CreateAndLoadSegment("output_cues.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + const Track* const track = segment_->GetTracks()->GetTrackByIndex(0); + const Cues* const cues = segment_->GetCues(); + ASSERT_TRUE(cues != NULL); + while (!cues->DoneParsing()) { + cues->LoadCuePoint(); + } + EXPECT_EQ(3, cues->GetCount()); + + // Get first Cue Point + const CuePoint* cue_point = cues->GetFirst(); + CompareCuePointContents(track, cue_point, 0, kVideoTrackNumber, 206); + + // Get second Cue Point + cue_point = cues->GetNext(cue_point); + CompareCuePointContents(track, cue_point, 6000000, kVideoTrackNumber, 269); + + // Get third (also last) Cue Point + cue_point = cues->GetNext(cue_point); + const CuePoint* last_cue_point = cues->GetLast(); + EXPECT_TRUE(cue_point == last_cue_point); + CompareCuePointContents(track, cue_point, 4000000, kVideoTrackNumber, 269); + + EXPECT_TRUE(ValidateCues(segment_, &reader_)); +} + +TEST_F(ParserTest, CuesBeforeClusters) { + ASSERT_TRUE(CreateAndLoadSegment("cues_before_clusters.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + const Track* const track = segment_->GetTracks()->GetTrackByIndex(0); + const Cues* const cues = segment_->GetCues(); + ASSERT_TRUE(cues != NULL); + while (!cues->DoneParsing()) { + cues->LoadCuePoint(); + } + EXPECT_EQ(2, cues->GetCount()); + + // Get first Cue Point + const CuePoint* cue_point = cues->GetFirst(); + CompareCuePointContents(track, cue_point, 0, kVideoTrackNumber, 238); + + // Get second (also last) Cue Point + cue_point = cues->GetNext(cue_point); + const CuePoint* last_cue_point = cues->GetLast(); + EXPECT_TRUE(cue_point == last_cue_point); + CompareCuePointContents(track, cue_point, 6000000, kVideoTrackNumber, 301); + + EXPECT_TRUE(ValidateCues(segment_, &reader_)); +} + +TEST_F(ParserTest, CuesTrackNumber) { + ASSERT_TRUE(CreateAndLoadSegment("set_cues_track_number.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + const Track* const track = segment_->GetTracks()->GetTrackByIndex(0); + const Cues* const cues = segment_->GetCues(); + ASSERT_TRUE(cues != NULL); + while (!cues->DoneParsing()) { + cues->LoadCuePoint(); + } + EXPECT_EQ(2, cues->GetCount()); + + // Get first Cue Point + const CuePoint* cue_point = cues->GetFirst(); + CompareCuePointContents(track, cue_point, 0, 10, 206); + + // Get second (also last) Cue Point + cue_point = cues->GetNext(cue_point); + const CuePoint* last_cue_point = cues->GetLast(); + EXPECT_TRUE(cue_point == last_cue_point); + CompareCuePointContents(track, cue_point, 6000000, 10, 269); + + EXPECT_TRUE(ValidateCues(segment_, &reader_)); +} + +TEST_F(ParserTest, Opus) { + ASSERT_TRUE(CreateAndLoadSegment("bbb_480p_vp9_opus_1second.webm", 4)); + const unsigned int kTracksCount = 2; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + // -------------------------------------------------------------------------- + // Track Header validation. + const Tracks* const tracks = segment_->GetTracks(); + EXPECT_EQ(kTracksCount, tracks->GetTracksCount()); + for (int i = 0; i < 2; ++i) { + const Track* const track = tracks->GetTrackByIndex(i); + ASSERT_TRUE(track != NULL); + + EXPECT_EQ(NULL, track->GetNameAsUTF8()); + EXPECT_STREQ("und", track->GetLanguage()); + EXPECT_EQ(i + 1, track->GetNumber()); + EXPECT_FALSE(track->GetLacing()); + + if (track->GetType() == Track::kVideo) { + const VideoTrack* const video_track = + dynamic_cast<const VideoTrack*>(track); + EXPECT_EQ(854, static_cast<int>(video_track->GetWidth())); + EXPECT_EQ(480, static_cast<int>(video_track->GetHeight())); + EXPECT_STREQ(kVP9CodecId, video_track->GetCodecId()); + EXPECT_DOUBLE_EQ(0., video_track->GetFrameRate()); + EXPECT_EQ(41666666, + static_cast<int>(video_track->GetDefaultDuration())); // 24.000 + const unsigned int kVideoUid = kVideoTrackNumber; + EXPECT_EQ(kVideoUid, video_track->GetUid()); + const unsigned int kCodecDelay = 0; + EXPECT_EQ(kCodecDelay, video_track->GetCodecDelay()); + const unsigned int kSeekPreRoll = 0; + EXPECT_EQ(kSeekPreRoll, video_track->GetSeekPreRoll()); + + size_t video_codec_private_size; + EXPECT_EQ(NULL, video_track->GetCodecPrivate(video_codec_private_size)); + const unsigned int kPrivateSize = 0; + EXPECT_EQ(kPrivateSize, video_codec_private_size); + } else if (track->GetType() == Track::kAudio) { + const AudioTrack* const audio_track = + dynamic_cast<const AudioTrack*>(track); + EXPECT_EQ(48000, audio_track->GetSamplingRate()); + EXPECT_EQ(6, audio_track->GetChannels()); + EXPECT_EQ(32, audio_track->GetBitDepth()); + EXPECT_STREQ(kOpusCodecId, audio_track->GetCodecId()); + EXPECT_EQ(kAudioTrackNumber, static_cast<int>(audio_track->GetUid())); + const unsigned int kDefaultDuration = 0; + EXPECT_EQ(kDefaultDuration, audio_track->GetDefaultDuration()); + EXPECT_EQ(kOpusCodecDelay, audio_track->GetCodecDelay()); + EXPECT_EQ(kOpusSeekPreroll, audio_track->GetSeekPreRoll()); + + size_t audio_codec_private_size; + EXPECT_TRUE(audio_track->GetCodecPrivate(audio_codec_private_size) != + NULL); + EXPECT_GE(audio_codec_private_size, kOpusPrivateDataSizeMinimum); + } + } + + // -------------------------------------------------------------------------- + // Parse the file to do block-level validation. + const Cluster* cluster = segment_->GetFirst(); + ASSERT_TRUE(cluster != NULL); + EXPECT_FALSE(cluster->EOS()); + + for (; cluster != NULL && !cluster->EOS(); + cluster = segment_->GetNext(cluster)) { + // Get the first block + const BlockEntry* block_entry; + EXPECT_EQ(0, cluster->GetFirst(block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + + while (block_entry != NULL && !block_entry->EOS()) { + const Block* const block = block_entry->GetBlock(); + ASSERT_TRUE(block != NULL); + EXPECT_FALSE(block->IsInvisible()); + EXPECT_EQ(Block::kLacingNone, block->GetLacing()); + + const std::uint32_t track_number = + static_cast<std::uint32_t>(block->GetTrackNumber()); + const Track* const track = tracks->GetTrackByNumber(track_number); + ASSERT_TRUE(track != NULL); + EXPECT_EQ(track->GetNumber(), block->GetTrackNumber()); + const unsigned int kContentEncodingCount = 0; + EXPECT_EQ(kContentEncodingCount, + track->GetContentEncodingCount()); // no encryption + + const std::int64_t track_type = track->GetType(); + EXPECT_TRUE(track_type == Track::kVideo || track_type == Track::kAudio); + if (track_type == Track::kVideo) { + EXPECT_EQ(BlockEntry::kBlockSimple, block_entry->GetKind()); + EXPECT_EQ(0, block->GetDiscardPadding()); + } else { + EXPECT_TRUE(block->IsKey()); + const std::int64_t kLastAudioTimecode = 1001; + const std::int64_t timecode = block->GetTimeCode(cluster); + // Only the final Opus block should have discard padding. + if (timecode == kLastAudioTimecode) { + EXPECT_EQ(BlockEntry::kBlockGroup, block_entry->GetKind()); + EXPECT_EQ(13500000, block->GetDiscardPadding()); + } else { + EXPECT_EQ(BlockEntry::kBlockSimple, block_entry->GetKind()); + EXPECT_EQ(0, block->GetDiscardPadding()); + } + } + + const int frame_count = block->GetFrameCount(); + const Block::Frame& frame = block->GetFrame(0); + EXPECT_EQ(1, frame_count); + EXPECT_GT(frame.len, 0); + + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + } + } + + ASSERT_TRUE(cluster != NULL); + EXPECT_TRUE(cluster->EOS()); +} + +TEST_F(ParserTest, DiscardPadding) { + // Test an artificial file with some extreme DiscardPadding values. + const std::string file = "discard_padding.webm"; + ASSERT_TRUE(CreateAndLoadSegment(file, 4)); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + // -------------------------------------------------------------------------- + // Track Header validation. + const Tracks* const tracks = segment_->GetTracks(); + EXPECT_EQ(kTracksCount, tracks->GetTracksCount()); + const Track* const track = tracks->GetTrackByIndex(0); + ASSERT_TRUE(track != NULL); + + EXPECT_STREQ(NULL, track->GetNameAsUTF8()); + EXPECT_EQ(NULL, track->GetLanguage()); + EXPECT_EQ(kAudioTrackNumber, track->GetNumber()); + EXPECT_TRUE(track->GetLacing()); + + EXPECT_EQ(Track::kAudio, track->GetType()); + const AudioTrack* const audio_track = dynamic_cast<const AudioTrack*>(track); + EXPECT_EQ(30, audio_track->GetSamplingRate()); + EXPECT_EQ(2, audio_track->GetChannels()); + EXPECT_STREQ(kOpusCodecId, audio_track->GetCodecId()); + EXPECT_EQ(kAudioTrackNumber, static_cast<int>(audio_track->GetUid())); + const unsigned int kDefaultDuration = 0; + EXPECT_EQ(kDefaultDuration, audio_track->GetDefaultDuration()); + const unsigned int kCodecDelay = 0; + EXPECT_EQ(kCodecDelay, audio_track->GetCodecDelay()); + const unsigned int kSeekPreRoll = 0; + EXPECT_EQ(kSeekPreRoll, audio_track->GetSeekPreRoll()); + + size_t audio_codec_private_size; + EXPECT_EQ(NULL, audio_track->GetCodecPrivate(audio_codec_private_size)); + const unsigned int kPrivateSize = 0; + EXPECT_EQ(kPrivateSize, audio_codec_private_size); + + // -------------------------------------------------------------------------- + // Parse the file to do block-level validation. + const Cluster* cluster = segment_->GetFirst(); + ASSERT_TRUE(cluster != NULL); + EXPECT_FALSE(cluster->EOS()); + const unsigned int kSegmentCount = 1; + EXPECT_EQ(kSegmentCount, segment_->GetCount()); + + // Get the first block + const BlockEntry* block_entry; + EXPECT_EQ(0, cluster->GetFirst(block_entry)); + ASSERT_TRUE(block_entry != NULL); + EXPECT_FALSE(block_entry->EOS()); + + const std::array<int, 3> discard_padding = {{12810000, 127, -128}}; + int index = 0; + while (block_entry != NULL && !block_entry->EOS()) { + const Block* const block = block_entry->GetBlock(); + ASSERT_TRUE(block != NULL); + EXPECT_FALSE(block->IsInvisible()); + EXPECT_EQ(Block::kLacingNone, block->GetLacing()); + + const std::uint32_t track_number = + static_cast<std::uint32_t>(block->GetTrackNumber()); + const Track* const track = tracks->GetTrackByNumber(track_number); + ASSERT_TRUE(track != NULL); + EXPECT_EQ(track->GetNumber(), block->GetTrackNumber()); + const unsigned int kContentEncodingCount = 0; + EXPECT_EQ(kContentEncodingCount, + track->GetContentEncodingCount()); // no encryption + + const std::int64_t track_type = track->GetType(); + EXPECT_EQ(Track::kAudio, track_type); + EXPECT_TRUE(block->IsKey()); + + // All blocks have DiscardPadding. + EXPECT_EQ(BlockEntry::kBlockGroup, block_entry->GetKind()); + ASSERT_LT(index, static_cast<int>(discard_padding.size())); + EXPECT_EQ(discard_padding[index], block->GetDiscardPadding()); + ++index; + + const int frame_count = block->GetFrameCount(); + const Block::Frame& frame = block->GetFrame(0); + EXPECT_EQ(1, frame_count); + EXPECT_GT(frame.len, 0); + + EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry)); + } + + cluster = segment_->GetNext(cluster); + ASSERT_TRUE(cluster != NULL); + EXPECT_TRUE(cluster->EOS()); +} + +TEST_F(ParserTest, StereoModeParsedCorrectly) { + ASSERT_TRUE(CreateAndLoadSegment("test_stereo_left_right.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + + const VideoTrack* const video_track = dynamic_cast<const VideoTrack*>( + segment_->GetTracks()->GetTrackByIndex(0)); + + EXPECT_EQ(1, video_track->GetStereoMode()); + EXPECT_EQ(256, video_track->GetWidth()); + EXPECT_EQ(144, video_track->GetHeight()); + EXPECT_EQ(128, video_track->GetDisplayWidth()); + EXPECT_EQ(144, video_track->GetDisplayHeight()); +} + +TEST_F(ParserTest, CanParseColour) { + ASSERT_TRUE(CreateAndLoadSegment("colour.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + const VideoTrack* const video_track = dynamic_cast<const VideoTrack*>( + segment_->GetTracks()->GetTrackByIndex(0)); + + const mkvparser::Colour* const colour = video_track->GetColour(); + ASSERT_TRUE(colour != nullptr); + EXPECT_EQ(0u, colour->matrix_coefficients); + EXPECT_EQ(1u, colour->bits_per_channel); + EXPECT_EQ(2u, colour->chroma_subsampling_horz); + EXPECT_EQ(3u, colour->chroma_subsampling_vert); + EXPECT_EQ(4u, colour->cb_subsampling_horz); + EXPECT_EQ(5u, colour->cb_subsampling_vert); + EXPECT_EQ(1u, colour->chroma_siting_horz); + EXPECT_EQ(1u, colour->chroma_siting_vert); + EXPECT_EQ(2u, colour->range); + EXPECT_EQ(9u, colour->transfer_characteristics); + EXPECT_EQ(10u, colour->primaries); + EXPECT_EQ(11u, colour->max_cll); + EXPECT_EQ(12u, colour->max_fall); + + const mkvparser::MasteringMetadata* const mm = + video_track->GetColour()->mastering_metadata; + ASSERT_TRUE(mm != nullptr); + ASSERT_TRUE(mm->r != nullptr); + ASSERT_TRUE(mm->g != nullptr); + ASSERT_TRUE(mm->b != nullptr); + ASSERT_TRUE(mm->white_point != nullptr); + EXPECT_FLOAT_EQ(.1, mm->r->x); + EXPECT_FLOAT_EQ(.2, mm->r->y); + EXPECT_FLOAT_EQ(.1, mm->g->x); + EXPECT_FLOAT_EQ(.2, mm->g->y); + EXPECT_FLOAT_EQ(.1, mm->b->x); + EXPECT_FLOAT_EQ(.2, mm->b->y); + EXPECT_FLOAT_EQ(.1, mm->white_point->x); + EXPECT_FLOAT_EQ(.2, mm->white_point->y); + EXPECT_FLOAT_EQ(30.0, mm->luminance_min); + EXPECT_FLOAT_EQ(40.0, mm->luminance_max); +} + +TEST_F(ParserTest, CanParseProjection) { + ASSERT_TRUE(CreateAndLoadSegment("projection.webm")); + const unsigned int kTracksCount = 1; + EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount()); + const VideoTrack* const video_track = + static_cast<const VideoTrack*>(segment_->GetTracks()->GetTrackByIndex(0)); + + const mkvparser::Projection* const projection = video_track->GetProjection(); + ASSERT_TRUE(projection != nullptr); + EXPECT_EQ(mkvparser::Projection::kRectangular, projection->type); + EXPECT_FLOAT_EQ(1, projection->pose_yaw); + EXPECT_FLOAT_EQ(2, projection->pose_pitch); + EXPECT_FLOAT_EQ(3, projection->pose_roll); + EXPECT_EQ(1u, projection->private_data_length); + ASSERT_TRUE(projection->private_data != nullptr); + EXPECT_EQ(4u, projection->private_data[0]); +} + +TEST_F(ParserTest, Vp9CodecLevelTest) { + const int kCodecPrivateLength = 3; + const uint8_t good_codec_private_level[kCodecPrivateLength] = {2, 1, 11}; + libwebm::Vp9CodecFeatures features; + EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_level[0], + kCodecPrivateLength, &features)); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.profile); + EXPECT_EQ(11, features.level); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.bit_depth); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, + features.chroma_subsampling); +} + +TEST_F(ParserTest, Vp9CodecProfileTest) { + const int kCodecPrivateLength = 3; + const uint8_t good_codec_private_profile[kCodecPrivateLength] = {1, 1, 1}; + libwebm::Vp9CodecFeatures features; + EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_profile[0], + kCodecPrivateLength, &features)); + EXPECT_EQ(1, features.profile); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.level); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.bit_depth); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, + features.chroma_subsampling); +} + +TEST_F(ParserTest, Vp9CodecBitDepthTest) { + const int kCodecPrivateLength = 3; + const uint8_t good_codec_private_profile[kCodecPrivateLength] = {3, 1, 8}; + libwebm::Vp9CodecFeatures features; + EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_profile[0], + kCodecPrivateLength, &features)); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.profile); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.level); + EXPECT_EQ(8, features.bit_depth); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, + features.chroma_subsampling); +} + +TEST_F(ParserTest, Vp9CodecChromaSubsamplingTest) { + const int kCodecPrivateLength = 3; + const uint8_t good_codec_private_profile[kCodecPrivateLength] = {4, 1, 0}; + libwebm::Vp9CodecFeatures features; + EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_profile[0], + kCodecPrivateLength, &features)); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.profile); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.level); + EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.bit_depth); + EXPECT_EQ(0, features.chroma_subsampling); +} + +TEST_F(ParserTest, Vp9CodecProfileLevelTest) { + const int kCodecPrivateLength = 6; + const uint8_t codec_private[kCodecPrivateLength] = {1, 1, 1, 2, 1, 11}; + libwebm::Vp9CodecFeatures features; + EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&codec_private[0], + kCodecPrivateLength, &features)); + EXPECT_EQ(1, features.profile); + EXPECT_EQ(11, features.level); +} + +TEST_F(ParserTest, Vp9CodecAllTest) { + const int kCodecPrivateLength = 12; + const uint8_t codec_private[kCodecPrivateLength] = {1, 1, 1, 2, 1, 11, + 3, 1, 8, 4, 1, 0}; + libwebm::Vp9CodecFeatures features; + EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&codec_private[0], + kCodecPrivateLength, &features)); + EXPECT_EQ(1, features.profile); + EXPECT_EQ(11, features.level); + EXPECT_EQ(8, features.bit_depth); + EXPECT_EQ(0, features.chroma_subsampling); +} + +TEST_F(ParserTest, Vp9CodecPrivateBadTest) { + const int kCodecPrivateLength = 3; + libwebm::Vp9CodecFeatures features; + // Test invalid codec private data; all of these should return false. + const uint8_t bad_codec_private[kCodecPrivateLength] = {0, 0, 0}; + EXPECT_FALSE( + libwebm::ParseVpxCodecPrivate(NULL, kCodecPrivateLength, &features)); + EXPECT_FALSE( + libwebm::ParseVpxCodecPrivate(&bad_codec_private[0], 0, &features)); + EXPECT_FALSE(libwebm::ParseVpxCodecPrivate(&bad_codec_private[0], + kCodecPrivateLength, &features)); + const uint8_t good_codec_private_level[kCodecPrivateLength] = {2, 1, 11}; + + // Test parse of codec private chunks, but lie about length. + EXPECT_FALSE( + libwebm::ParseVpxCodecPrivate(&bad_codec_private[0], 0, &features)); + EXPECT_FALSE(libwebm::ParseVpxCodecPrivate(&good_codec_private_level[0], 0, + &features)); + EXPECT_FALSE(libwebm::ParseVpxCodecPrivate(&good_codec_private_level[0], + kCodecPrivateLength, NULL)); +} + +TEST_F(ParserTest, InvalidTruncatedChapterString) { + ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks( + "invalid/chapters_truncated_chapter_string.mkv")); + EXPECT_EQ(mkvparser::E_PARSE_FAILED, segment_->Load()); +} + +TEST_F(ParserTest, InvalidTruncatedChapterString2) { + ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks( + "invalid/chapters_truncated_chapter_string_2.mkv")); + EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, segment_->Load()); +} + +TEST_F(ParserTest, InvalidFixedLacingSize) { + ASSERT_NO_FATAL_FAILURE( + CreateSegmentNoHeaderChecks("invalid/fixed_lacing_bad_lace_size.mkv")); + ASSERT_EQ(0, segment_->Load()); + const mkvparser::BlockEntry* block_entry = NULL; + EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, + segment_->GetFirst()->GetFirst(block_entry)); +} + +TEST_F(ParserTest, InvalidBlockEndsBeyondCluster) { + ASSERT_NO_FATAL_FAILURE( + CreateSegmentNoHeaderChecks("invalid/block_ends_beyond_cluster.mkv")); + ASSERT_EQ(0, segment_->Load()); + const mkvparser::BlockEntry* block_entry = NULL; + EXPECT_EQ(0, segment_->GetFirst()->GetFirst(block_entry)); + EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, + segment_->GetFirst()->GetNext(block_entry, block_entry)); +} + +TEST_F(ParserTest, InvalidBlockGroupBlockEndsBlockGroup) { + ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks( + "invalid/blockgroup_block_ends_beyond_blockgroup.mkv")); + ASSERT_EQ(0, segment_->Load()); + const mkvparser::BlockEntry* block_entry = NULL; + EXPECT_EQ(0, segment_->GetFirst()->GetFirst(block_entry)); + EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, + segment_->GetFirst()->GetNext(block_entry, block_entry)); +} + +TEST_F(ParserTest, InvalidProjectionFloatOverflow) { + ASSERT_NO_FATAL_FAILURE( + CreateSegmentNoHeaderChecks("invalid/projection_float_overflow.webm")); + EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, segment_->Load()); +} + +TEST_F(ParserTest, InvalidPrimaryChromaticityParseFail) { + ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks( + "invalid/primarychromaticity_fieldtoolarge.webm")); + EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, segment_->Load()); +} + +} // namespace test + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/testing/test_util.cc b/testing/test_util.cc new file mode 100644 index 0000000..8789b33 --- /dev/null +++ b/testing/test_util.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "testing/test_util.h" + +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ios> +#include <string> + +#include "common/libwebm_util.h" +#include "common/webmids.h" + +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" + +namespace test { + +std::string GetTestDataDir() { + const char* test_data_path = std::getenv("LIBWEBM_TEST_DATA_PATH"); + return test_data_path ? std::string(test_data_path) : std::string(); +} + +std::string GetTestFilePath(const std::string& name) { + const std::string libwebm_testdata_dir = GetTestDataDir(); + return libwebm_testdata_dir + "/" + name; +} + +bool CompareFiles(const std::string& file1, const std::string& file2) { + const std::size_t kBlockSize = 4096; + std::uint8_t buf1[kBlockSize] = {0}; + std::uint8_t buf2[kBlockSize] = {0}; + + libwebm::FilePtr f1 = + libwebm::FilePtr(std::fopen(file1.c_str(), "rb"), libwebm::FILEDeleter()); + libwebm::FilePtr f2 = + libwebm::FilePtr(std::fopen(file2.c_str(), "rb"), libwebm::FILEDeleter()); + + if (!f1.get() || !f2.get()) { + // Files cannot match if one or both couldn't be opened. + return false; + } + + do { + const std::size_t r1 = std::fread(buf1, 1, kBlockSize, f1.get()); + const std::size_t r2 = std::fread(buf2, 1, kBlockSize, f2.get()); + + // TODO(fgalligan): Add output of which byte differs. + if (r1 != r2 || std::memcmp(buf1, buf2, r1)) { + return 0; // Files are not equal + } + } while (!std::feof(f1.get()) && !std::feof(f2.get())); + + return std::feof(f1.get()) && std::feof(f2.get()); +} + +bool HasCuePoints(const mkvparser::Segment* segment, + std::int64_t* cues_offset) { + if (!segment || !cues_offset) { + return false; + } + using mkvparser::SeekHead; + const SeekHead* const seek_head = segment->GetSeekHead(); + if (!seek_head) { + return false; + } + + std::int64_t offset = 0; + for (int i = 0; i < seek_head->GetCount(); ++i) { + const SeekHead::Entry* const entry = seek_head->GetEntry(i); + if (entry->id == libwebm::kMkvCues) { + offset = entry->pos; + } + } + + if (offset <= 0) { + // No Cues found. + return false; + } + + *cues_offset = offset; + return true; +} + +bool ValidateCues(mkvparser::Segment* segment, mkvparser::IMkvReader* reader) { + if (!segment) { + return false; + } + + std::int64_t cues_offset = 0; + if (!HasCuePoints(segment, &cues_offset)) { + // No cues to validate, everything is OK. + return true; + } + + // Parse Cues. + long long cues_pos = 0; // NOLINT + long cues_len = 0; // NOLINT + if (segment->ParseCues(cues_offset, cues_pos, cues_len)) { + return false; + } + + // Get a pointer to the video track if it exists. Otherwise, we assume + // that Cues are based on the first track (which is true for all our test + // files). + const mkvparser::Tracks* const tracks = segment->GetTracks(); + const mkvparser::Track* cues_track = tracks->GetTrackByIndex(0); + for (int i = 1; i < static_cast<int>(tracks->GetTracksCount()); ++i) { + const mkvparser::Track* const track = tracks->GetTrackByIndex(i); + if (track->GetType() == mkvparser::Track::kVideo) { + cues_track = track; + break; + } + } + + // Iterate through Cues and verify if they are pointing to the correct + // Cluster position. + const mkvparser::Cues* const cues = segment->GetCues(); + const mkvparser::CuePoint* cue_point = NULL; + while (cues->LoadCuePoint()) { + if (!cue_point) { + cue_point = cues->GetFirst(); + } else { + cue_point = cues->GetNext(cue_point); + } + const mkvparser::CuePoint::TrackPosition* const track_position = + cue_point->Find(cues_track); + const long long cluster_pos = track_position->m_pos + // NOLINT + segment->m_start; + + // If a cluster does not begin at |cluster_pos|, then the file is + // incorrect. + long length; // NOLINT + const std::int64_t id = mkvparser::ReadID(reader, cluster_pos, length); + if (id != libwebm::kMkvCluster) { + return false; + } + } + return true; +} + +MkvParser::~MkvParser() { + delete segment; + delete reader; +} + +bool ParseMkvFileReleaseParser(const std::string& webm_file, + MkvParser* parser_out) { + parser_out->reader = new (std::nothrow) mkvparser::MkvReader; + mkvparser::MkvReader& reader = *parser_out->reader; + if (!parser_out->reader || reader.Open(webm_file.c_str()) < 0) { + return false; + } + + long long pos = 0; // NOLINT + mkvparser::EBMLHeader ebml_header; + if (ebml_header.Parse(&reader, pos)) { + return false; + } + + using mkvparser::Segment; + Segment* segment_ptr = nullptr; + if (Segment::CreateInstance(&reader, pos, segment_ptr)) { + return false; + } + + std::unique_ptr<Segment> segment(segment_ptr); + long result; + if ((result = segment->Load()) < 0) { + return false; + } + + const mkvparser::Cluster* cluster = segment->GetFirst(); + if (!cluster || cluster->EOS()) { + return false; + } + + while (cluster && cluster->EOS() == false) { + if (cluster->GetTimeCode() < 0) { + return false; + } + + const mkvparser::BlockEntry* block = nullptr; + if (cluster->GetFirst(block) < 0) { + return false; + } + + while (block != NULL && block->EOS() == false) { + if (cluster->GetNext(block, block) < 0) { + return false; + } + } + + cluster = segment->GetNext(cluster); + } + + parser_out->segment = segment.release(); + return true; +} + +bool ParseMkvFile(const std::string& webm_file) { + MkvParser parser; + const bool result = ParseMkvFileReleaseParser(webm_file, &parser); + delete parser.segment; + delete parser.reader; + return result; +} + +} // namespace test diff --git a/testing/test_util.h b/testing/test_util.h new file mode 100644 index 0000000..5f85ec7 --- /dev/null +++ b/testing/test_util.h @@ -0,0 +1,88 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_TESTING_TEST_UTIL_H_ +#define LIBWEBM_TESTING_TEST_UTIL_H_ + +#include <stdint.h> + +#include <cstddef> +#include <string> + +namespace mkvparser { +class IMkvReader; +class MkvReader; +class Segment; +} // namespace mkvparser + +namespace test { + +// constants for muxer and parser tests +const char kAppString[] = "mkvmuxer_unit_tests"; +const char kOpusCodecId[] = "A_OPUS"; +const char kVorbisCodecId[] = "A_VORBIS"; +const int kAudioTrackNumber = 2; +const int kBitDepth = 2; +const int kChannels = 2; +const double kDuration = 2.345; +const int kFrameLength = 10; +const int kHeight = 180; +const int kInvalidTrackNumber = 100; +const std::uint64_t kOpusCodecDelay = 6500000; +const std::size_t kOpusPrivateDataSizeMinimum = 19; +const std::uint64_t kOpusSeekPreroll = 80000000; +const char kMetadataCodecId[] = "D_WEBVTT/METADATA"; +const int kMetadataTrackNumber = 3; +const int kMetadataTrackType = 0x21; +const int kSampleRate = 30; +const int kTimeCodeScale = 1000; +const char kTrackName[] = "unit_test"; +const char kVP8CodecId[] = "V_VP8"; +const char kVP9CodecId[] = "V_VP9"; +const double kVideoFrameRate = 0.5; +const int kVideoTrackNumber = 1; +const int kWidth = 320; + +// Returns the path to the test data directory by reading and returning the +// contents the LIBWEBM_TESTDATA_DIR environment variable. +std::string GetTestDataDir(); + +// Returns the absolute path to the file of |name| in LIBWEBM_TESTDATA_DIR. +std::string GetTestFilePath(const std::string& name); + +// Byte-wise comparison of two files |file1| and |file2|. Returns true if the +// files match exactly, false otherwise. +bool CompareFiles(const std::string& file1, const std::string& file2); + +// Returns true and sets |cues_offset| to the cues location within the MKV file +// parsed by |segment| when the MKV file has cue points. +bool HasCuePoints(const mkvparser::Segment* segment, std::int64_t* cues_offset); + +// Validates cue points. Assumes caller has already called Load() on |segment|. +// Returns true when: +// All cue points point at clusters, OR +// Data parsed by |segment| has no cue points. +bool ValidateCues(mkvparser::Segment* segment, mkvparser::IMkvReader* reader); + +// Parses |webm_file| using mkvparser and returns true when file parses +// successfully (all clusters and blocks can be successfully walked). Second +// variant allows further interaction with the parsed file via transferring +// ownership of the mkvparser Segment and MkvReader to the caller via +// |parser_out|. +struct MkvParser { + MkvParser() = default; + ~MkvParser(); + mkvparser::Segment* segment = nullptr; + mkvparser::MkvReader* reader = nullptr; +}; +bool ParseMkvFile(const std::string& webm_file); +bool ParseMkvFileReleaseParser(const std::string& webm_file, + MkvParser* parser_out); + +} // namespace test + +#endif // LIBWEBM_TESTING_TEST_UTIL_H_
\ No newline at end of file diff --git a/testing/testdata/accurate_cluster_duration.webm b/testing/testdata/accurate_cluster_duration.webm Binary files differnew file mode 100644 index 0000000..fffe83d --- /dev/null +++ b/testing/testdata/accurate_cluster_duration.webm diff --git a/testing/testdata/accurate_cluster_duration_last_frame.webm b/testing/testdata/accurate_cluster_duration_last_frame.webm Binary files differnew file mode 100644 index 0000000..8104bfc --- /dev/null +++ b/testing/testdata/accurate_cluster_duration_last_frame.webm diff --git a/testing/testdata/accurate_cluster_duration_two_tracks.webm b/testing/testdata/accurate_cluster_duration_two_tracks.webm Binary files differnew file mode 100644 index 0000000..cf69859 --- /dev/null +++ b/testing/testdata/accurate_cluster_duration_two_tracks.webm diff --git a/testing/testdata/bbb_480p_vp9_opus_1second.webm b/testing/testdata/bbb_480p_vp9_opus_1second.webm Binary files differnew file mode 100644 index 0000000..c3657a7 --- /dev/null +++ b/testing/testdata/bbb_480p_vp9_opus_1second.webm diff --git a/testing/testdata/block_with_additional.webm b/testing/testdata/block_with_additional.webm Binary files differnew file mode 100644 index 0000000..2f1b22c --- /dev/null +++ b/testing/testdata/block_with_additional.webm diff --git a/testing/testdata/chapters.webm b/testing/testdata/chapters.webm Binary files differnew file mode 100644 index 0000000..af0c5fe --- /dev/null +++ b/testing/testdata/chapters.webm diff --git a/testing/testdata/colour.webm b/testing/testdata/colour.webm Binary files differnew file mode 100644 index 0000000..580cb50 --- /dev/null +++ b/testing/testdata/colour.webm diff --git a/testing/testdata/cues_before_clusters.webm b/testing/testdata/cues_before_clusters.webm Binary files differnew file mode 100644 index 0000000..c634b17 --- /dev/null +++ b/testing/testdata/cues_before_clusters.webm diff --git a/testing/testdata/discard_padding.webm b/testing/testdata/discard_padding.webm Binary files differnew file mode 100644 index 0000000..a573226 --- /dev/null +++ b/testing/testdata/discard_padding.webm diff --git a/testing/testdata/estimate_duration.webm b/testing/testdata/estimate_duration.webm Binary files differnew file mode 100644 index 0000000..c754741 --- /dev/null +++ b/testing/testdata/estimate_duration.webm diff --git a/testing/testdata/fixed_size_cluster_timecode.webm b/testing/testdata/fixed_size_cluster_timecode.webm Binary files differnew file mode 100644 index 0000000..c6f1cc6 --- /dev/null +++ b/testing/testdata/fixed_size_cluster_timecode.webm diff --git a/testing/testdata/force_new_cluster.webm b/testing/testdata/force_new_cluster.webm Binary files differnew file mode 100644 index 0000000..f762b07 --- /dev/null +++ b/testing/testdata/force_new_cluster.webm diff --git a/testing/testdata/invalid/README.libwebm b/testing/testdata/invalid/README.libwebm new file mode 100644 index 0000000..f9e9413 --- /dev/null +++ b/testing/testdata/invalid/README.libwebm @@ -0,0 +1,24 @@ +Why the files in this directory are considered invalid: + +block_ends_beyond_cluster.mkv - + File containing a single cluster with two simple blocks. One valid, and the + second reporting a size that would cause the block to end far beyond the end + of its parent cluster. + +blockgroup_block_ends_beyond_blockgroup.mkv - + File containing a single cluster and two blockgroups. The first blockgroup is + valid. The second blockgroup contains a block reporting a size that spans well + past the block and the end of the file. + +chapters_truncated_chapter_string.mkv - + File with a Chapters element that ends with a ChapterAtom whose ChapterDisplay + element contains a truncated ChapterString. + +chapters_truncated_chapter_string_2.mkv - + Nearly identical to chapters_truncated_chapter_string.mkv, but with a void + element and a partial cluster. Causes mkvparser to fail in a slightly + different manner. + +fixed_lacing_bad_lace_size.mkv - + File containing a BlockGroup with fixed lacing, but reports a total laced size + that is not evenly divisible by the number of laced frames. diff --git a/testing/testdata/invalid/block_ends_beyond_cluster.mkv b/testing/testdata/invalid/block_ends_beyond_cluster.mkv Binary files differnew file mode 100644 index 0000000..3035c8c --- /dev/null +++ b/testing/testdata/invalid/block_ends_beyond_cluster.mkv diff --git a/testing/testdata/invalid/blockgroup_block_ends_beyond_blockgroup.mkv b/testing/testdata/invalid/blockgroup_block_ends_beyond_blockgroup.mkv Binary files differnew file mode 100644 index 0000000..da9aca9 --- /dev/null +++ b/testing/testdata/invalid/blockgroup_block_ends_beyond_blockgroup.mkv diff --git a/testing/testdata/invalid/chapters_truncated_chapter_string.mkv b/testing/testdata/invalid/chapters_truncated_chapter_string.mkv Binary files differnew file mode 100644 index 0000000..9883e77 --- /dev/null +++ b/testing/testdata/invalid/chapters_truncated_chapter_string.mkv diff --git a/testing/testdata/invalid/chapters_truncated_chapter_string_2.mkv b/testing/testdata/invalid/chapters_truncated_chapter_string_2.mkv Binary files differnew file mode 100644 index 0000000..37f0403 --- /dev/null +++ b/testing/testdata/invalid/chapters_truncated_chapter_string_2.mkv diff --git a/testing/testdata/invalid/fixed_lacing_bad_lace_size.mkv b/testing/testdata/invalid/fixed_lacing_bad_lace_size.mkv Binary files differnew file mode 100644 index 0000000..bbe2025 --- /dev/null +++ b/testing/testdata/invalid/fixed_lacing_bad_lace_size.mkv diff --git a/testing/testdata/invalid/invalid_vp9_bitstream-bug_1416.webm b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1416.webm Binary files differnew file mode 100644 index 0000000..ac76dce --- /dev/null +++ b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1416.webm diff --git a/testing/testdata/invalid/invalid_vp9_bitstream-bug_1417.webm b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1417.webm Binary files differnew file mode 100644 index 0000000..0cbd724 --- /dev/null +++ b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1417.webm diff --git a/testing/testdata/invalid/primarychromaticity_fieldtoolarge.webm b/testing/testdata/invalid/primarychromaticity_fieldtoolarge.webm new file mode 100644 index 0000000..c432425 --- /dev/null +++ b/testing/testdata/invalid/primarychromaticity_fieldtoolarge.webm @@ -0,0 +1 @@ +EߣŸB00B00B00B00B‚„webmB00B00S€g0000000»‹00000000000»Œ000000000000ì¼000000000000000000000000000000000000000000000000000000000000€“0000000000000000000W0“0000000000000000000T®k@ž®@›×0s00ƒ†…00000à@‡°‚00º0U°ýU¸0U¹0Uº0U»0U¼0U½0UÐÆUÙ„0000UÚ„0000UÑ„0000UÒ000000000000000000000000000000000000000000000000000000000000000000000000000
\ No newline at end of file diff --git a/testing/testdata/invalid/projection_float_overflow.webm b/testing/testdata/invalid/projection_float_overflow.webm Binary files differnew file mode 100644 index 0000000..46f6208 --- /dev/null +++ b/testing/testdata/invalid/projection_float_overflow.webm diff --git a/testing/testdata/long_tag_string.webm b/testing/testdata/long_tag_string.webm Binary files differnew file mode 100644 index 0000000..e8d6653 --- /dev/null +++ b/testing/testdata/long_tag_string.webm diff --git a/testing/testdata/matroska_doctype.mkv b/testing/testdata/matroska_doctype.mkv Binary files differnew file mode 100644 index 0000000..5652785 --- /dev/null +++ b/testing/testdata/matroska_doctype.mkv diff --git a/testing/testdata/max_cluster_duration.webm b/testing/testdata/max_cluster_duration.webm Binary files differnew file mode 100644 index 0000000..9caad9c --- /dev/null +++ b/testing/testdata/max_cluster_duration.webm diff --git a/testing/testdata/max_cluster_size.webm b/testing/testdata/max_cluster_size.webm Binary files differnew file mode 100644 index 0000000..df87536 --- /dev/null +++ b/testing/testdata/max_cluster_size.webm diff --git a/testing/testdata/metadata_block.webm b/testing/testdata/metadata_block.webm Binary files differnew file mode 100644 index 0000000..a9c686e --- /dev/null +++ b/testing/testdata/metadata_block.webm diff --git a/testing/testdata/output_cues.webm b/testing/testdata/output_cues.webm Binary files differnew file mode 100644 index 0000000..49e2126 --- /dev/null +++ b/testing/testdata/output_cues.webm diff --git a/testing/testdata/projection.webm b/testing/testdata/projection.webm Binary files differnew file mode 100644 index 0000000..0f13e8a --- /dev/null +++ b/testing/testdata/projection.webm diff --git a/testing/testdata/segment_duration.webm b/testing/testdata/segment_duration.webm Binary files differnew file mode 100644 index 0000000..47cfb6d --- /dev/null +++ b/testing/testdata/segment_duration.webm diff --git a/testing/testdata/segment_info.webm b/testing/testdata/segment_info.webm Binary files differnew file mode 100644 index 0000000..0fde8f0 --- /dev/null +++ b/testing/testdata/segment_info.webm diff --git a/testing/testdata/set_cues_track_number.webm b/testing/testdata/set_cues_track_number.webm Binary files differnew file mode 100644 index 0000000..82a2b38 --- /dev/null +++ b/testing/testdata/set_cues_track_number.webm diff --git a/testing/testdata/set_pixelwidth_pixelheight.webm b/testing/testdata/set_pixelwidth_pixelheight.webm Binary files differnew file mode 100644 index 0000000..e81c8bc --- /dev/null +++ b/testing/testdata/set_pixelwidth_pixelheight.webm diff --git a/testing/testdata/set_segment_duration.webm b/testing/testdata/set_segment_duration.webm Binary files differnew file mode 100644 index 0000000..100684b --- /dev/null +++ b/testing/testdata/set_segment_duration.webm diff --git a/testing/testdata/simple_block.webm b/testing/testdata/simple_block.webm Binary files differnew file mode 100644 index 0000000..c311e68 --- /dev/null +++ b/testing/testdata/simple_block.webm diff --git a/testing/testdata/test_stereo_left_right.webm b/testing/testdata/test_stereo_left_right.webm Binary files differnew file mode 100644 index 0000000..8c717dc --- /dev/null +++ b/testing/testdata/test_stereo_left_right.webm diff --git a/testing/testdata/tracks.webm b/testing/testdata/tracks.webm Binary files differnew file mode 100644 index 0000000..8aa9274 --- /dev/null +++ b/testing/testdata/tracks.webm diff --git a/testing/testdata/webm_doctype.webm b/testing/testdata/webm_doctype.webm Binary files differnew file mode 100644 index 0000000..4269030 --- /dev/null +++ b/testing/testdata/webm_doctype.webm diff --git a/testing/video_frame_tests.cc b/testing/video_frame_tests.cc new file mode 100644 index 0000000..4e5b397 --- /dev/null +++ b/testing/video_frame_tests.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "common/video_frame.h" + +#include "gtest/gtest.h" + +namespace { +const libwebm::VideoFrame::Codec kCodec = libwebm::VideoFrame::kVP8; +const std::int64_t kPts = 12345; +const std::size_t kSize = 1; +const std::size_t kEmptySize = 0; + +TEST(VideoFrameTests, DefaultsTest) { + libwebm::VideoFrame frame; + EXPECT_EQ(kEmptySize, frame.buffer().capacity); + EXPECT_EQ(kEmptySize, frame.buffer().length); + EXPECT_EQ(nullptr, frame.buffer().data.get()); + EXPECT_FALSE(frame.keyframe()); + EXPECT_EQ(0, frame.nanosecond_pts()); + EXPECT_EQ(libwebm::VideoFrame::kVP9, frame.codec()); +} + +TEST(VideoFrameTests, SizeTest) { + libwebm::VideoFrame frame; + EXPECT_TRUE(frame.Init(kSize)); + + // Buffer inits empty, length should be 0, aka |kEmpty|. + EXPECT_GT(kSize, frame.buffer().length); + EXPECT_EQ(kEmptySize, frame.buffer().length); + + // Capacity should be equal to |kSize|. + EXPECT_EQ(kSize, frame.buffer().capacity); + EXPECT_FALSE(frame.SetBufferLength(kSize + 1)); + + // Write a byte into the buffer via the raw data pointer, update length, and + // verify expected behavior. + uint8_t* write_ptr = reinterpret_cast<uint8_t*>(frame.buffer().data.get()); + *write_ptr = 0xFF; + EXPECT_TRUE(frame.SetBufferLength(1)); + EXPECT_EQ(frame.buffer().length, frame.buffer().capacity); +} + +TEST(VideoFrameTests, OverloadsTest) { + const bool kKeyframe = true; + + // Test VideoFrame::VideoFrame(bool keyframe, int64_t nano_pts, Codec c). + libwebm::VideoFrame keyframe(kKeyframe, kPts, kCodec); + EXPECT_EQ(kKeyframe, keyframe.keyframe()); + EXPECT_EQ(kPts, keyframe.nanosecond_pts()); + EXPECT_EQ(kCodec, keyframe.codec()); + EXPECT_EQ(kEmptySize, keyframe.buffer().capacity); + EXPECT_EQ(kEmptySize, keyframe.buffer().length); + EXPECT_EQ(nullptr, keyframe.buffer().data.get()); + + // Test VideoFrame::Init(std::size_t length). + EXPECT_TRUE(keyframe.Init(kSize)); + EXPECT_EQ(kKeyframe, keyframe.keyframe()); + EXPECT_EQ(kPts, keyframe.nanosecond_pts()); + EXPECT_EQ(kCodec, keyframe.codec()); + EXPECT_NE(nullptr, keyframe.buffer().data.get()); + + // Test VideoFrame::Init(size_t length, int64_t nano_pts, Codec c). + EXPECT_TRUE(keyframe.Init(kSize, kPts + 1, libwebm::VideoFrame::kVP9)); + EXPECT_EQ(kSize, keyframe.buffer().capacity); + EXPECT_GT(kSize, keyframe.buffer().length); + EXPECT_NE(kPts, keyframe.nanosecond_pts()); + EXPECT_NE(kCodec, keyframe.codec()); +} + +} // namespace diff --git a/vttdemux.cc b/vttdemux.cc new file mode 100644 index 0000000..186783b --- /dev/null +++ b/vttdemux.cc @@ -0,0 +1,1004 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" +#include "webvtt/webvttparser.h" + +using std::string; + +namespace libwebm { +namespace vttdemux { + +typedef long long mkvtime_t; // NOLINT +typedef long long mkvpos_t; // NOLINT +typedef std::unique_ptr<mkvparser::Segment> segment_ptr_t; + +// WebVTT metadata tracks have a type (encoded in the CodecID for the track). +// We use |type| to synthesize a filename for the out-of-band WebVTT |file|. +struct MetadataInfo { + enum Type { kSubtitles, kCaptions, kDescriptions, kMetadata, kChapters } type; + FILE* file; +}; + +// We use a map, indexed by track number, to collect information about +// each track in the input file. +typedef std::map<long, MetadataInfo> metadata_map_t; // NOLINT + +// The distinguished key value we use to store the chapters +// information in the metadata map. +enum { kChaptersKey = 0 }; + +// The data from the original WebVTT Cue is stored as a WebM block. +// The FrameParser is used to parse the lines of text out from the +// block, in order to reconstruct the original WebVTT Cue. +class FrameParser : public libwebvtt::LineReader { + public: + // Bind the FrameParser instance to a WebM block. + explicit FrameParser(const mkvparser::BlockGroup* block_group); + virtual ~FrameParser(); + + // The Webm block (group) to which this instance is bound. We + // treat the payload of the block as a stream of characters. + const mkvparser::BlockGroup* const block_group_; + + protected: + // Read the next character from the character stream (the payload + // of the WebM block). We increment the stream pointer |pos_| as + // each character from the stream is consumed. + virtual int GetChar(char* c); + + // End-of-line handling requires that we put a character back into + // the stream. Here we need only decrement the stream pointer |pos_| + // to unconsume the character. + virtual void UngetChar(char c); + + // The current position in the character stream (the payload of the block). + mkvpos_t pos_; + + // The position of the end of the character stream. When the current + // position |pos_| equals the end position |pos_end_|, the entire + // stream (block payload) has been consumed and end-of-stream is indicated. + mkvpos_t pos_end_; + + private: + // Disable copy ctor and copy assign + FrameParser(const FrameParser&); + FrameParser& operator=(const FrameParser&); +}; + +// The data from the original WebVTT Cue is stored as an MKV Chapters +// Atom element (the cue payload is stored as a Display sub-element). +// The ChapterAtomParser is used to parse the lines of text out from +// the String sub-element of the Display element (though it would be +// admittedly odd if there were more than one line). +class ChapterAtomParser : public libwebvtt::LineReader { + public: + explicit ChapterAtomParser(const mkvparser::Chapters::Display* display); + virtual ~ChapterAtomParser(); + + const mkvparser::Chapters::Display* const display_; + + protected: + // Read the next character from the character stream (the title + // member of the atom's display). We increment the stream pointer + // |str_| as each character from the stream is consumed. + virtual int GetChar(char* c); + + // End-of-line handling requires that we put a character back into + // the stream. Here we need only decrement the stream pointer |str_| + // to unconsume the character. + virtual void UngetChar(char c); + + // The current position in the character stream (the title of the + // atom's display). + const char* str_; + + // The position of the end of the character stream. When the current + // position |str_| equals the end position |str_end_|, the entire + // stream (title of the display) has been consumed and end-of-stream + // is indicated. + const char* str_end_; + + private: + ChapterAtomParser(const ChapterAtomParser&); + ChapterAtomParser& operator=(const ChapterAtomParser&); +}; + +// Parse the EBML header of the WebM input file, to determine whether we +// actually have a WebM file. Returns false if this is not a WebM file. +bool ParseHeader(mkvparser::IMkvReader* reader, mkvpos_t* pos); + +// Parse the Segment of the input file and load all of its clusters. +// Returns false if there was an error parsing the file. +bool ParseSegment(mkvparser::IMkvReader* reader, mkvpos_t pos, + segment_ptr_t* segment); + +// If |segment| has a Chapters element (in which case, there will be a +// corresponding entry in |metadata_map|), convert the MKV chapters to +// WebVTT chapter cues and write them to the output file. Returns +// false on error. +bool WriteChaptersFile(const metadata_map_t& metadata_map, + const mkvparser::Segment* segment); + +// Convert an MKV Chapters Atom to a WebVTT cue and write it to the +// output |file|. Returns false on error. +bool WriteChaptersCue(FILE* file, const mkvparser::Chapters* chapters, + const mkvparser::Chapters::Atom* atom, + const mkvparser::Chapters::Display* display); + +// Write the Cue Identifier line of the WebVTT cue, if it's present. +// Returns false on error. +bool WriteChaptersCueIdentifier(FILE* file, + const mkvparser::Chapters::Atom* atom); + +// Use the timecodes from the chapters |atom| to write just the +// timings line of the WebVTT cue. Returns false on error. +bool WriteChaptersCueTimings(FILE* file, const mkvparser::Chapters* chapters, + const mkvparser::Chapters::Atom* atom); + +// Parse the String sub-element of the |display| and write the payload +// of the WebVTT cue. Returns false on error. +bool WriteChaptersCuePayload(FILE* file, + const mkvparser::Chapters::Display* display); + +// Iterate over the tracks of the input file (and any chapters +// element) and cache information about each metadata track. +void BuildMap(const mkvparser::Segment* segment, metadata_map_t* metadata_map); + +// For each track listed in the cache, synthesize its output filename +// and open a file handle that designates the out-of-band file. +// Returns false if we were unable to open an output file for a track. +bool OpenFiles(metadata_map_t* metadata_map, const char* filename); + +// Close the file handle for each track in the cache. +void CloseFiles(metadata_map_t* metadata_map); + +// Iterate over the clusters of the input file, and write a WebVTT cue +// for each metadata block. Returns false if processing of a cluster +// failed. +bool WriteFiles(const metadata_map_t& m, mkvparser::Segment* s); + +// Write the WebVTT header for each track in the cache. We do this +// immediately before writing the actual WebVTT cues. Returns false +// if the write failed. +bool InitializeFiles(const metadata_map_t& metadata_map); + +// Iterate over the blocks of the |cluster|, writing a WebVTT cue to +// its associated output file for each block of metadata. Returns +// false if processing a block failed, or there was a parse error. +bool ProcessCluster(const metadata_map_t& metadata_map, + const mkvparser::Cluster* cluster); + +// Look up this track number in the cache, and if found (meaning this +// is a metadata track), write a WebVTT cue to the associated output +// file. Returns false if writing the WebVTT cue failed. +bool ProcessBlockEntry(const metadata_map_t& metadata_map, + const mkvparser::BlockEntry* block_entry); + +// Parse the lines of text from the |block_group| to reconstruct the +// original WebVTT cue, and write it to the associated output |file|. +// Returns false if there was an error writing to the output file. +bool WriteCue(FILE* file, const mkvparser::BlockGroup* block_group); + +// Consume a line of text from the character stream, and if the line +// is not empty write the cue identifier to the associated output +// file. Returns false if there was an error writing to the file. +bool WriteCueIdentifier(FILE* f, FrameParser* parser); + +// Consume a line of text from the character stream (which holds any +// cue settings) and write the cue timings line for this cue to the +// associated output file. Returns false if there was an error +// writing to the file. +bool WriteCueTimings(FILE* f, FrameParser* parser); + +// Write the timestamp (representating either the start time or stop +// time of the cue) to the output file. Returns false if there was an +// error writing to the file. +bool WriteCueTime(FILE* f, mkvtime_t time_ns); + +// Consume the remaining lines of text from the character stream +// (these lines are the actual payload of the WebVTT cue), and write +// them to the associated output file. Returns false if there was an +// error writing to the file. +bool WriteCuePayload(FILE* f, FrameParser* parser); +} // namespace vttdemux + +namespace vttdemux { + +FrameParser::FrameParser(const mkvparser::BlockGroup* block_group) + : block_group_(block_group) { + const mkvparser::Block* const block = block_group->GetBlock(); + const mkvparser::Block::Frame& f = block->GetFrame(0); + + // The beginning and end of the character stream corresponds to the + // position of this block's frame within the WebM input file. + + pos_ = f.pos; + pos_end_ = f.pos + f.len; +} + +FrameParser::~FrameParser() {} + +int FrameParser::GetChar(char* c) { + if (pos_ >= pos_end_) // end-of-stream + return 1; // per the semantics of libwebvtt::Reader::GetChar + + const mkvparser::Cluster* const cluster = block_group_->GetCluster(); + const mkvparser::Segment* const segment = cluster->m_pSegment; + mkvparser::IMkvReader* const reader = segment->m_pReader; + + unsigned char* const buf = reinterpret_cast<unsigned char*>(c); + const int result = reader->Read(pos_, 1, buf); + + if (result < 0) // error + return -1; + + ++pos_; // consume this character in the stream + return 0; +} + +void FrameParser::UngetChar(char /* c */) { + // All we need to do here is decrement the position in the stream. + // The next time GetChar is called the same character will be + // re-read from the input file. + --pos_; +} + +ChapterAtomParser::ChapterAtomParser( + const mkvparser::Chapters::Display* display) + : display_(display) { + str_ = display->GetString(); + if (str_ == NULL) + return; + const size_t len = strlen(str_); + str_end_ = str_ + len; +} + +ChapterAtomParser::~ChapterAtomParser() {} + +int ChapterAtomParser::GetChar(char* c) { + if (str_ == NULL || str_ >= str_end_) // end-of-stream + return 1; // per the semantics of libwebvtt::Reader::GetChar + + *c = *str_++; // consume this character in the stream + return 0; +} + +void ChapterAtomParser::UngetChar(char /* c */) { + // All we need to do here is decrement the position in the stream. + // The next time GetChar is called the same character will be + // re-read from the input file. + --str_; +} + +} // namespace vttdemux + +bool vttdemux::ParseHeader(mkvparser::IMkvReader* reader, mkvpos_t* pos) { + mkvparser::EBMLHeader h; + const mkvpos_t status = h.Parse(reader, *pos); + + if (status) { + printf("error parsing EBML header\n"); + return false; + } + + if (h.m_docType == NULL || strcmp(h.m_docType, "webm") != 0) { + printf("bad doctype\n"); + return false; + } + + return true; // success +} + +bool vttdemux::ParseSegment(mkvparser::IMkvReader* reader, mkvpos_t pos, + segment_ptr_t* segment_ptr) { + // We first create the segment object. + + mkvparser::Segment* p; + const mkvpos_t create = mkvparser::Segment::CreateInstance(reader, pos, p); + + if (create) { + printf("error parsing segment element\n"); + return false; + } + + segment_ptr->reset(p); + + // Now parse all of the segment's sub-elements, in toto. + + const long status = p->Load(); // NOLINT + + if (status < 0) { + printf("error loading segment\n"); + return false; + } + + return true; +} + +void vttdemux::BuildMap(const mkvparser::Segment* segment, + metadata_map_t* map_ptr) { + metadata_map_t& m = *map_ptr; + m.clear(); + + if (segment->GetChapters()) { + MetadataInfo info; + info.file = NULL; + info.type = MetadataInfo::kChapters; + + m[kChaptersKey] = info; + } + + const mkvparser::Tracks* const tt = segment->GetTracks(); + if (tt == NULL) + return; + + const long tc = tt->GetTracksCount(); // NOLINT + if (tc <= 0) + return; + + // Iterate over the tracks in the intput file. We determine whether + // a track holds metadata by inspecting its CodecID. + + for (long idx = 0; idx < tc; ++idx) { // NOLINT + const mkvparser::Track* const t = tt->GetTrackByIndex(idx); + + if (t == NULL) // weird + continue; + + const long tn = t->GetNumber(); // NOLINT + + if (tn <= 0) // weird + continue; + + const char* const codec_id = t->GetCodecId(); + + if (codec_id == NULL) // weird + continue; + + MetadataInfo info; + info.file = NULL; + + if (strcmp(codec_id, "D_WEBVTT/SUBTITLES") == 0) { + info.type = MetadataInfo::kSubtitles; + } else if (strcmp(codec_id, "D_WEBVTT/CAPTIONS") == 0) { + info.type = MetadataInfo::kCaptions; + } else if (strcmp(codec_id, "D_WEBVTT/DESCRIPTIONS") == 0) { + info.type = MetadataInfo::kDescriptions; + } else if (strcmp(codec_id, "D_WEBVTT/METADATA") == 0) { + info.type = MetadataInfo::kMetadata; + } else { + continue; + } + + m[tn] = info; // create an entry in the cache for this track + } +} + +bool vttdemux::OpenFiles(metadata_map_t* metadata_map, const char* filename) { + if (metadata_map == NULL || metadata_map->empty()) + return false; + + if (filename == NULL) + return false; + + // Find the position of the filename extension. We synthesize the + // output filename from the directory path and basename of the input + // filename. + + const char* const ext = strrchr(filename, '.'); + + if (ext == NULL) // TODO(matthewjheaney): liberalize? + return false; + + // Remember whether a track of this type has already been seen (the + // map key) by keeping a count (the map item). We quality the + // output filename with the track number if there is more than one + // track having a given type. + + std::map<MetadataInfo::Type, int> exists; + + typedef metadata_map_t::iterator iter_t; + + metadata_map_t& m = *metadata_map; + const iter_t ii = m.begin(); + const iter_t j = m.end(); + + // Make a first pass over the cache to determine whether there is + // more than one track corresponding to a given metadata type. + + iter_t i = ii; + while (i != j) { + const metadata_map_t::value_type& v = *i++; + const MetadataInfo& info = v.second; + const MetadataInfo::Type type = info.type; + ++exists[type]; + } + + // Make a second pass over the cache, synthesizing the filename of + // each output file (from the input file basename, the input track + // metadata type, and its track number if necessary), and then + // opening a WebVTT output file having that filename. + + i = ii; + while (i != j) { + metadata_map_t::value_type& v = *i++; + MetadataInfo& info = v.second; + const MetadataInfo::Type type = info.type; + + // Start with the basename of the input file. + + string name(filename, ext); + + // Next append the metadata kind. + + switch (type) { + case MetadataInfo::kSubtitles: + name += "_SUBTITLES"; + break; + + case MetadataInfo::kCaptions: + name += "_CAPTIONS"; + break; + + case MetadataInfo::kDescriptions: + name += "_DESCRIPTIONS"; + break; + + case MetadataInfo::kMetadata: + name += "_METADATA"; + break; + + case MetadataInfo::kChapters: + name += "_CHAPTERS"; + break; + + default: + return false; + } + + // If there is more than one metadata track having a given type + // (the WebVTT-in-WebM spec doesn't preclude this), then qualify + // the output filename with the input track number. + + if (exists[type] > 1) { + enum { kLen = 33 }; + char str[kLen]; // max 126 tracks, so only 4 chars really needed +#ifndef _MSC_VER + snprintf(str, kLen, "%ld", v.first); // track number +#else + _snprintf_s(str, sizeof(str), kLen, "%ld", v.first); // track number +#endif + name += str; + } + + // Finally append the output filename extension. + + name += ".vtt"; + + // We have synthesized the full output filename, so attempt to + // open the WebVTT output file. + + info.file = fopen(name.c_str(), "wb"); + const bool success = (info.file != NULL); + + if (!success) { + printf("unable to open output file %s\n", name.c_str()); + return false; + } + } + + return true; +} + +void vttdemux::CloseFiles(metadata_map_t* metadata_map) { + if (metadata_map == NULL) + return; + + metadata_map_t& m = *metadata_map; + + typedef metadata_map_t::iterator iter_t; + + iter_t i = m.begin(); + const iter_t j = m.end(); + + // Gracefully close each output file, to ensure all output gets + // propertly flushed. + + while (i != j) { + metadata_map_t::value_type& v = *i++; + MetadataInfo& info = v.second; + + if (info.file != NULL) { + fclose(info.file); + info.file = NULL; + } + } +} + +bool vttdemux::WriteFiles(const metadata_map_t& m, mkvparser::Segment* s) { + // First write the WebVTT header. + + InitializeFiles(m); + + if (!WriteChaptersFile(m, s)) + return false; + + // Now iterate over the clusters, writing the WebVTT cue as we parse + // each metadata block. + + const mkvparser::Cluster* cluster = s->GetFirst(); + + while (cluster != NULL && !cluster->EOS()) { + if (!ProcessCluster(m, cluster)) + return false; + + cluster = s->GetNext(cluster); + } + + return true; +} + +bool vttdemux::InitializeFiles(const metadata_map_t& m) { + // Write the WebVTT header for each output file in the cache. + + typedef metadata_map_t::const_iterator iter_t; + iter_t i = m.begin(); + const iter_t j = m.end(); + + while (i != j) { + const metadata_map_t::value_type& v = *i++; + const MetadataInfo& info = v.second; + FILE* const f = info.file; + + if (fputs("WEBVTT\n", f) < 0) { + printf("unable to initialize output file\n"); + return false; + } + } + + return true; +} + +bool vttdemux::WriteChaptersFile(const metadata_map_t& m, + const mkvparser::Segment* s) { + const metadata_map_t::const_iterator info_iter = m.find(kChaptersKey); + if (info_iter == m.end()) // no chapters, so nothing to do + return true; + + const mkvparser::Chapters* const chapters = s->GetChapters(); + if (chapters == NULL) // weird + return true; + + const MetadataInfo& info = info_iter->second; + FILE* const file = info.file; + + const int edition_count = chapters->GetEditionCount(); + + if (edition_count <= 0) // weird + return true; // nothing to do + + if (edition_count > 1) { + // TODO(matthewjheaney): figure what to do here + printf("more than one chapter edition detected\n"); + return false; + } + + const mkvparser::Chapters::Edition* const edition = chapters->GetEdition(0); + + const int atom_count = edition->GetAtomCount(); + + for (int idx = 0; idx < atom_count; ++idx) { + const mkvparser::Chapters::Atom* const atom = edition->GetAtom(idx); + const int display_count = atom->GetDisplayCount(); + + if (display_count <= 0) + continue; + + if (display_count > 1) { + // TODO(matthewjheaney): handle case of multiple languages + printf("more than 1 display in atom detected\n"); + return false; + } + + const mkvparser::Chapters::Display* const display = atom->GetDisplay(0); + + if (const char* language = display->GetLanguage()) { + if (strcmp(language, "eng") != 0) { + // TODO(matthewjheaney): handle case of multiple languages. + + // We must create a separate webvtt file for each language. + // This isn't a simple problem (which is why we defer it for + // now), because there's nothing in the header that tells us + // what languages we have as cues. We must parse the displays + // of each atom to determine that. + + // One solution is to make two passes over the input data. + // First parse the displays, creating an in-memory cache of + // all the chapter cues, sorted according to their language. + // After we have read all of the chapter atoms from the input + // file, we can then write separate output files for each + // language. + + printf("only English-language chapter cues are supported\n"); + return false; + } + } + + if (!WriteChaptersCue(file, chapters, atom, display)) + return false; + } + + return true; +} + +bool vttdemux::WriteChaptersCue(FILE* f, const mkvparser::Chapters* chapters, + const mkvparser::Chapters::Atom* atom, + const mkvparser::Chapters::Display* display) { + // We start a new cue by writing a cue separator (an empty line) + // into the stream. + + if (fputc('\n', f) < 0) + return false; + + // A WebVTT Cue comprises 3 things: a cue identifier, followed by + // the cue timings, followed by the payload of the cue. We write + // each part of the cue in sequence. + + if (!WriteChaptersCueIdentifier(f, atom)) + return false; + + if (!WriteChaptersCueTimings(f, chapters, atom)) + return false; + + if (!WriteChaptersCuePayload(f, display)) + return false; + + return true; +} + +bool vttdemux::WriteChaptersCueIdentifier( + FILE* f, const mkvparser::Chapters::Atom* atom) { + const char* const identifier = atom->GetStringUID(); + + if (identifier == NULL) + return true; // nothing else to do + + if (fprintf(f, "%s\n", identifier) < 0) + return false; + + return true; +} + +bool vttdemux::WriteChaptersCueTimings(FILE* f, + const mkvparser::Chapters* chapters, + const mkvparser::Chapters::Atom* atom) { + const mkvtime_t start_ns = atom->GetStartTime(chapters); + + if (start_ns < 0) + return false; + + const mkvtime_t stop_ns = atom->GetStopTime(chapters); + + if (stop_ns < 0) + return false; + + if (!WriteCueTime(f, start_ns)) + return false; + + if (fputs(" --> ", f) < 0) + return false; + + if (!WriteCueTime(f, stop_ns)) + return false; + + if (fputc('\n', f) < 0) + return false; + + return true; +} + +bool vttdemux::WriteChaptersCuePayload( + FILE* f, const mkvparser::Chapters::Display* display) { + // Bind a Chapter parser object to the display, which allows us to + // extract each line of text from the title-part of the display. + ChapterAtomParser parser(display); + + int count = 0; // count of lines of payload text written to output file + for (string line;;) { + const int e = parser.GetLine(&line); + + if (e < 0) // error (only -- we allow EOS here) + return false; + + if (line.empty()) // TODO(matthewjheaney): retain this check? + break; + + if (fprintf(f, "%s\n", line.c_str()) < 0) + return false; + + ++count; + } + + if (count <= 0) // WebVTT cue requires non-empty payload + return false; + + return true; +} + +bool vttdemux::ProcessCluster(const metadata_map_t& m, + const mkvparser::Cluster* c) { + // Visit the blocks in this cluster, writing a WebVTT cue for each + // metadata block. + + const mkvparser::BlockEntry* block_entry; + + long result = c->GetFirst(block_entry); // NOLINT + if (result < 0) { + printf("bad cluster (unable to get first block)\n"); + return false; + } + + while (block_entry != NULL && !block_entry->EOS()) { + if (!ProcessBlockEntry(m, block_entry)) + return false; + + result = c->GetNext(block_entry, block_entry); + if (result < 0) { // error + printf("bad cluster (unable to get next block)\n"); + return false; + } + } + + return true; +} + +bool vttdemux::ProcessBlockEntry(const metadata_map_t& m, + const mkvparser::BlockEntry* block_entry) { + // If the track number for this block is in the cache, then we have + // a metadata block, so write the WebVTT cue to the output file. + + const mkvparser::Block* const block = block_entry->GetBlock(); + const long long tn = block->GetTrackNumber(); // NOLINT + + typedef metadata_map_t::const_iterator iter_t; + const iter_t i = m.find(static_cast<metadata_map_t::key_type>(tn)); + + if (i == m.end()) // not a metadata track + return true; // nothing else to do + + if (block_entry->GetKind() != mkvparser::BlockEntry::kBlockGroup) + return false; // weird + + typedef mkvparser::BlockGroup BG; + const BG* const block_group = static_cast<const BG*>(block_entry); + + const MetadataInfo& info = i->second; + FILE* const f = info.file; + + return WriteCue(f, block_group); +} + +bool vttdemux::WriteCue(FILE* f, const mkvparser::BlockGroup* block_group) { + // Bind a FrameParser object to the block, which allows us to + // extract each line of text from the payload of the block. + FrameParser parser(block_group); + + // We start a new cue by writing a cue separator (an empty line) + // into the stream. + + if (fputc('\n', f) < 0) + return false; + + // A WebVTT Cue comprises 3 things: a cue identifier, followed by + // the cue timings, followed by the payload of the cue. We write + // each part of the cue in sequence. + + if (!WriteCueIdentifier(f, &parser)) + return false; + + if (!WriteCueTimings(f, &parser)) + return false; + + if (!WriteCuePayload(f, &parser)) + return false; + + return true; +} + +bool vttdemux::WriteCueIdentifier(FILE* f, FrameParser* parser) { + string line; + int e = parser->GetLine(&line); + + if (e) // error or EOS + return false; + + // If the cue identifier line is empty, this means that the original + // WebVTT cue did not have a cue identifier, so we don't bother + // writing an extra line terminator to the output file (though doing + // so would be harmless). + + if (!line.empty()) { + if (fputs(line.c_str(), f) < 0) + return false; + + if (fputc('\n', f) < 0) + return false; + } + + return true; +} + +bool vttdemux::WriteCueTimings(FILE* f, FrameParser* parser) { + const mkvparser::BlockGroup* const block_group = parser->block_group_; + const mkvparser::Cluster* const cluster = block_group->GetCluster(); + const mkvparser::Block* const block = block_group->GetBlock(); + + // A WebVTT Cue "timings" line comprises two parts: the start and + // stop time for this cue, followed by the (optional) cue settings, + // such as orientation of the rendered text or its size. Only the + // settings part of the cue timings line is stored in the WebM + // block. We reconstruct the start and stop times of the WebVTT cue + // from the timestamp and duration of the WebM block. + + const mkvtime_t start_ns = block->GetTime(cluster); + + if (!WriteCueTime(f, start_ns)) + return false; + + if (fputs(" --> ", f) < 0) + return false; + + const mkvtime_t duration_timecode = block_group->GetDurationTimeCode(); + + if (duration_timecode < 0) + return false; + + const mkvparser::Segment* const segment = cluster->m_pSegment; + const mkvparser::SegmentInfo* const info = segment->GetInfo(); + + if (info == NULL) + return false; + + const mkvtime_t timecode_scale = info->GetTimeCodeScale(); + + if (timecode_scale <= 0) + return false; + + const mkvtime_t duration_ns = duration_timecode * timecode_scale; + const mkvtime_t stop_ns = start_ns + duration_ns; + + if (!WriteCueTime(f, stop_ns)) + return false; + + string line; + int e = parser->GetLine(&line); + + if (e) // error or EOS + return false; + + if (!line.empty()) { + if (fputc(' ', f) < 0) + return false; + + if (fputs(line.c_str(), f) < 0) + return false; + } + + if (fputc('\n', f) < 0) + return false; + + return true; +} + +bool vttdemux::WriteCueTime(FILE* f, mkvtime_t time_ns) { + mkvtime_t ms = time_ns / 1000000; // WebVTT time has millisecond resolution + + mkvtime_t sec = ms / 1000; + ms -= sec * 1000; + + mkvtime_t min = sec / 60; + sec -= 60 * min; + + mkvtime_t hr = min / 60; + min -= 60 * hr; + + if (hr > 0) { + if (fprintf(f, "%02lld:", hr) < 0) + return false; + } + + if (fprintf(f, "%02lld:%02lld.%03lld", min, sec, ms) < 0) + return false; + + return true; +} + +bool vttdemux::WriteCuePayload(FILE* f, FrameParser* parser) { + int count = 0; // count of lines of payload text written to output file + for (string line;;) { + const int e = parser->GetLine(&line); + + if (e < 0) // error (only -- we allow EOS here) + return false; + + if (line.empty()) // TODO(matthewjheaney): retain this check? + break; + + if (fprintf(f, "%s\n", line.c_str()) < 0) + return false; + + ++count; + } + + if (count <= 0) // WebVTT cue requires non-empty payload + return false; + + return true; +} + +} // namespace libwebm + +int main(int argc, const char* argv[]) { + if (argc != 2) { + printf("usage: vttdemux <webmfile>\n"); + return EXIT_SUCCESS; + } + + const char* const filename = argv[1]; + mkvparser::MkvReader reader; + + int e = reader.Open(filename); + + if (e) { // error + printf("unable to open file\n"); + return EXIT_FAILURE; + } + + libwebm::vttdemux::mkvpos_t pos; + + if (!libwebm::vttdemux::ParseHeader(&reader, &pos)) + return EXIT_FAILURE; + + libwebm::vttdemux::segment_ptr_t segment_ptr; + + if (!libwebm::vttdemux::ParseSegment(&reader, pos, &segment_ptr)) + return EXIT_FAILURE; + + libwebm::vttdemux::metadata_map_t metadata_map; + + BuildMap(segment_ptr.get(), &metadata_map); + + if (metadata_map.empty()) { + printf("no WebVTT metadata found\n"); + return EXIT_FAILURE; + } + + if (!OpenFiles(&metadata_map, filename)) { + CloseFiles(&metadata_map); // nothing to flush, so not strictly necessary + return EXIT_FAILURE; + } + + if (!WriteFiles(metadata_map, segment_ptr.get())) { + CloseFiles(&metadata_map); // might as well flush what we do have + return EXIT_FAILURE; + } + + CloseFiles(&metadata_map); + + return EXIT_SUCCESS; +} diff --git a/vttreader.h b/vttreader.h new file mode 100644 index 0000000..2e7cc4b --- /dev/null +++ b/vttreader.h @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_VTTREADER_H_ +#define LIBWEBM_VTTREADER_H_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "webvtt/vttreader.h" + +#endif // LIBWEBM_VTTREADER_H_ diff --git a/webm_info.cc b/webm_info.cc new file mode 100644 index 0000000..fdf4759 --- /dev/null +++ b/webm_info.cc @@ -0,0 +1,1329 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include <inttypes.h> +#include <stdint.h> + +#include <cstdlib> +#include <cstring> +#include <limits> +#include <memory> +#include <queue> +#include <string> +#include <vector> + +#include "common/hdr_util.h" +#include "common/indent.h" +#include "common/vp9_header_parser.h" +#include "common/vp9_level_stats.h" +#include "common/webm_constants.h" +#include "common/webm_endian.h" + +#include "mkvparser/mkvparser.h" +#include "mkvparser/mkvreader.h" + +namespace { + +using libwebm::Indent; +using libwebm::kNanosecondsPerSecond; +using libwebm::kNanosecondsPerSecondi; +using mkvparser::ContentEncoding; +using std::string; +using std::wstring; + +const char VERSION_STRING[] = "1.0.4.5"; + +struct Options { + Options(); + + // Returns true if |value| matches -|option| or -no|option|. + static bool MatchesBooleanOption(const string& option, const string& value); + + // Set all of the member variables to |value|. + void SetAll(bool value); + + bool output_video; + bool output_audio; + bool output_size; + bool output_offset; + bool output_seconds; + bool output_ebml_header; + bool output_segment; + bool output_seekhead; + bool output_segment_info; + bool output_tracks; + bool output_clusters; + bool output_blocks; + bool output_codec_info; + bool output_clusters_size; + bool output_encrypted_info; + bool output_cues; + bool output_frame_stats; + bool output_vp9_level; +}; + +Options::Options() + : output_video(true), + output_audio(true), + output_size(false), + output_offset(false), + output_seconds(true), + output_ebml_header(true), + output_segment(true), + output_seekhead(false), + output_segment_info(true), + output_tracks(true), + output_clusters(false), + output_blocks(false), + output_codec_info(false), + output_clusters_size(false), + output_encrypted_info(false), + output_cues(false), + output_frame_stats(false), + output_vp9_level(false) {} + +void Options::SetAll(bool value) { + output_video = value; + output_audio = value; + output_size = value; + output_offset = value; + output_ebml_header = value; + output_seconds = value; + output_segment = value; + output_segment_info = value; + output_tracks = value; + output_clusters = value; + output_blocks = value; + output_codec_info = value; + output_clusters_size = value; + output_encrypted_info = value; + output_cues = value; + output_frame_stats = value; + output_vp9_level = value; +} + +bool Options::MatchesBooleanOption(const string& option, const string& value) { + const string opt = "-" + option; + const string noopt = "-no" + option; + return value == opt || value == noopt; +} + +struct FrameStats { + FrameStats() + : frames(0), + displayed_frames(0), + first_altref(true), + frames_since_last_altref(0), + minimum_altref_distance(std::numeric_limits<int>::max()), + min_altref_end_ns(0), + max_window_size(0), + max_window_end_ns(0) {} + + int frames; + int displayed_frames; + + bool first_altref; + int frames_since_last_altref; + int minimum_altref_distance; + int64_t min_altref_end_ns; + + std::queue<int64_t> window; + int64_t max_window_size; + int64_t max_window_end_ns; +}; + +void Usage() { + printf("Usage: webm_info [options] -i input\n"); + printf("\n"); + printf("Main options:\n"); + printf(" -h | -? show help\n"); + printf(" -v show version\n"); + printf(" -all Enable all output options.\n"); + printf(" -video Output video tracks (true)\n"); + printf(" -audio Output audio tracks (true)\n"); + printf(" -size Output element sizes (false)\n"); + printf(" -offset Output element offsets (false)\n"); + printf(" -times_seconds Output times as seconds (true)\n"); + printf(" -ebml_header Output EBML header (true)\n"); + printf(" -segment Output Segment (true)\n"); + printf(" -seekhead Output SeekHead (false)\n"); + printf(" -segment_info Output SegmentInfo (true)\n"); + printf(" -tracks Output Tracks (true)\n"); + printf(" -clusters Output Clusters (false)\n"); + printf(" -blocks Output Blocks (false)\n"); + printf(" -codec_info Output video codec information (false)\n"); + printf(" -clusters_size Output Total Clusters size (false)\n"); + printf(" -encrypted_info Output encrypted frame info (false)\n"); + printf(" -cues Output Cues entries (false)\n"); + printf(" -frame_stats Output frame stats (VP9)(false)\n"); + printf(" -vp9_level Output VP9 level(false)\n"); + printf("\nOutput options may be negated by prefixing 'no'.\n"); +} + +// TODO(fgalligan): Add support for non-ascii. +wstring UTF8ToWideString(const char* str) { + wstring wstr; + + if (str == NULL) + return wstr; + + string temp_str(str, strlen(str)); + wstr.assign(temp_str.begin(), temp_str.end()); + + return wstr; +} + +string ToString(const char* str) { return string((str == NULL) ? "" : str); } + +void OutputEBMLHeader(const mkvparser::EBMLHeader& ebml, FILE* o, + Indent* indent) { + fprintf(o, "EBML Header:\n"); + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sEBMLVersion : %lld\n", indent->indent_str().c_str(), + ebml.m_version); + fprintf(o, "%sEBMLReadVersion : %lld\n", indent->indent_str().c_str(), + ebml.m_readVersion); + fprintf(o, "%sEBMLMaxIDLength : %lld\n", indent->indent_str().c_str(), + ebml.m_maxIdLength); + fprintf(o, "%sEBMLMaxSizeLength : %lld\n", indent->indent_str().c_str(), + ebml.m_maxSizeLength); + fprintf(o, "%sDoc Type : %s\n", indent->indent_str().c_str(), + ebml.m_docType); + fprintf(o, "%sDocTypeVersion : %lld\n", indent->indent_str().c_str(), + ebml.m_docTypeVersion); + fprintf(o, "%sDocTypeReadVersion: %lld\n", indent->indent_str().c_str(), + ebml.m_docTypeReadVersion); + indent->Adjust(libwebm::kDecreaseIndent); +} + +void OutputSegment(const mkvparser::Segment& segment, const Options& options, + FILE* o) { + fprintf(o, "Segment:"); + if (options.output_offset) + fprintf(o, " @: %lld", segment.m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", + segment.m_size + segment.m_start - segment.m_element_start); + fprintf(o, "\n"); +} + +bool OutputSeekHead(const mkvparser::Segment& segment, const Options& options, + FILE* o, Indent* indent) { + const mkvparser::SeekHead* const seekhead = segment.GetSeekHead(); + if (!seekhead) { + // SeekHeads are optional. + return true; + } + + fprintf(o, "%sSeekHead:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", seekhead->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", seekhead->m_element_size); + fprintf(o, "\n"); + + indent->Adjust(libwebm::kIncreaseIndent); + + for (int i = 0; i < seekhead->GetCount(); ++i) { + const mkvparser::SeekHead::Entry* const entry = seekhead->GetEntry(i); + if (!entry) { + fprintf(stderr, "Error retrieving SeekHead entry #%d\n", i); + return false; + } + + fprintf(o, "%sEntry[%d]", indent->indent_str().c_str(), i); + if (options.output_offset) + fprintf(o, " @: %lld", entry->element_start); + if (options.output_size) + fprintf(o, " size: %lld", entry->element_size); + fprintf(o, "\n"); + + indent->Adjust(libwebm::kIncreaseIndent); + std::string entry_indent = indent->indent_str(); + // TODO(jzern): 1) known ids could be stringified. 2) ids could be + // reencoded to EBML for ease of lookup. + fprintf(o, "%sSeek ID : %llx\n", entry_indent.c_str(), entry->id); + fprintf(o, "%sSeek position : %lld\n", entry_indent.c_str(), entry->pos); + indent->Adjust(libwebm::kDecreaseIndent); + } + + for (int i = 0; i < seekhead->GetVoidElementCount(); ++i) { + const mkvparser::SeekHead::VoidElement* const entry = + seekhead->GetVoidElement(i); + if (!entry) { + fprintf(stderr, "Error retrieving SeekHead void element #%d\n", i); + return false; + } + + fprintf(o, "%sVoid element[%d]", indent->indent_str().c_str(), i); + if (options.output_offset) + fprintf(o, " @: %lld", entry->element_start); + if (options.output_size) + fprintf(o, " size: %lld", entry->element_size); + fprintf(o, "\n"); + } + + indent->Adjust(libwebm::kDecreaseIndent); + return true; +} + +bool OutputSegmentInfo(const mkvparser::Segment& segment, + const Options& options, FILE* o, Indent* indent) { + const mkvparser::SegmentInfo* const segment_info = segment.GetInfo(); + if (!segment_info) { + fprintf(stderr, "SegmentInfo was NULL.\n"); + return false; + } + + const int64_t timecode_scale = segment_info->GetTimeCodeScale(); + const int64_t duration_ns = segment_info->GetDuration(); + const wstring title = UTF8ToWideString(segment_info->GetTitleAsUTF8()); + const wstring muxing_app = + UTF8ToWideString(segment_info->GetMuxingAppAsUTF8()); + const wstring writing_app = + UTF8ToWideString(segment_info->GetWritingAppAsUTF8()); + + fprintf(o, "%sSegmentInfo:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", segment_info->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", segment_info->m_element_size); + fprintf(o, "\n"); + + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sTimecodeScale : %" PRId64 " \n", indent->indent_str().c_str(), + timecode_scale); + if (options.output_seconds) + fprintf(o, "%sDuration(secs): %g\n", indent->indent_str().c_str(), + duration_ns / kNanosecondsPerSecond); + else + fprintf(o, "%sDuration(nano): %" PRId64 "\n", indent->indent_str().c_str(), + duration_ns); + + if (!title.empty()) + fprintf(o, "%sTitle : %ls\n", indent->indent_str().c_str(), + title.c_str()); + if (!muxing_app.empty()) + fprintf(o, "%sMuxingApp : %ls\n", indent->indent_str().c_str(), + muxing_app.c_str()); + if (!writing_app.empty()) + fprintf(o, "%sWritingApp : %ls\n", indent->indent_str().c_str(), + writing_app.c_str()); + indent->Adjust(libwebm::kDecreaseIndent); + return true; +} + +bool OutputTracks(const mkvparser::Segment& segment, const Options& options, + FILE* o, Indent* indent) { + const mkvparser::Tracks* const tracks = segment.GetTracks(); + if (!tracks) { + fprintf(stderr, "Tracks was NULL.\n"); + return false; + } + + fprintf(o, "%sTracks:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", tracks->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", tracks->m_element_size); + fprintf(o, "\n"); + + unsigned int i = 0; + const unsigned long j = tracks->GetTracksCount(); + while (i != j) { + const mkvparser::Track* const track = tracks->GetTrackByIndex(i++); + if (track == NULL) + continue; + + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sTrack:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", track->m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", track->m_element_size); + fprintf(o, "\n"); + + const int64_t track_type = track->GetType(); + const int64_t track_number = track->GetNumber(); + const wstring track_name = UTF8ToWideString(track->GetNameAsUTF8()); + + indent->Adjust(libwebm::kIncreaseIndent); + fprintf(o, "%sTrackType : %" PRId64 "\n", indent->indent_str().c_str(), + track_type); + fprintf(o, "%sTrackNumber : %" PRId64 "\n", indent->indent_str().c_str(), + track_number); + if (!track_name.empty()) + fprintf(o, "%sName : %ls\n", indent->indent_str().c_str(), + track_name.c_str()); + + const char* const codec_id = track->GetCodecId(); + if (codec_id) + fprintf(o, "%sCodecID : %s\n", indent->indent_str().c_str(), + codec_id); + + const wstring codec_name = UTF8ToWideString(track->GetCodecNameAsUTF8()); + if (!codec_name.empty()) + fprintf(o, "%sCodecName : %ls\n", indent->indent_str().c_str(), + codec_name.c_str()); + + size_t private_size; + const unsigned char* const private_data = + track->GetCodecPrivate(private_size); + if (private_data) { + fprintf(o, "%sPrivateData(size): %d\n", indent->indent_str().c_str(), + static_cast<int>(private_size)); + + if (track_type == mkvparser::Track::kVideo) { + const std::string codec_id = ToString(track->GetCodecId()); + const std::string v_vp9 = "V_VP9"; + if (codec_id == v_vp9) { + libwebm::Vp9CodecFeatures features; + if (!libwebm::ParseVpxCodecPrivate(private_data, + static_cast<int32_t>(private_size), + &features)) { + fprintf(stderr, "Error parsing VpxCodecPrivate.\n"); + return false; + } + if (features.profile != -1) + fprintf(o, "%sVP9 profile : %d\n", + indent->indent_str().c_str(), features.profile); + if (features.level != -1) + fprintf(o, "%sVP9 level : %d\n", + indent->indent_str().c_str(), features.level); + if (features.bit_depth != -1) + fprintf(o, "%sVP9 bit_depth : %d\n", + indent->indent_str().c_str(), features.bit_depth); + if (features.chroma_subsampling != -1) + fprintf(o, "%sVP9 chroma subsampling : %d\n", + indent->indent_str().c_str(), features.chroma_subsampling); + } + } + } + + const uint64_t default_duration = track->GetDefaultDuration(); + if (default_duration > 0) + fprintf(o, "%sDefaultDuration: %" PRIu64 "\n", + indent->indent_str().c_str(), default_duration); + + if (track->GetContentEncodingCount() > 0) { + // Only check the first content encoding. + const ContentEncoding* const encoding = + track->GetContentEncodingByIndex(0); + if (!encoding) { + printf("Could not get first ContentEncoding.\n"); + return false; + } + + fprintf(o, "%sContentEncodingOrder : %lld\n", + indent->indent_str().c_str(), encoding->encoding_order()); + fprintf(o, "%sContentEncodingScope : %lld\n", + indent->indent_str().c_str(), encoding->encoding_scope()); + fprintf(o, "%sContentEncodingType : %lld\n", + indent->indent_str().c_str(), encoding->encoding_type()); + + if (encoding->GetEncryptionCount() > 0) { + // Only check the first encryption. + const ContentEncoding::ContentEncryption* const encryption = + encoding->GetEncryptionByIndex(0); + if (!encryption) { + printf("Could not get first ContentEncryption.\n"); + return false; + } + + fprintf(o, "%sContentEncAlgo : %lld\n", + indent->indent_str().c_str(), encryption->algo); + + if (encryption->key_id_len > 0) { + fprintf(o, "%sContentEncKeyID : ", indent->indent_str().c_str()); + for (int k = 0; k < encryption->key_id_len; ++k) { + fprintf(o, "0x%02x, ", encryption->key_id[k]); + } + fprintf(o, "\n"); + } + + if (encryption->signature_len > 0) { + fprintf(o, "%sContentSignature : 0x", + indent->indent_str().c_str()); + for (int k = 0; k < encryption->signature_len; ++k) { + fprintf(o, "%x", encryption->signature[k]); + } + fprintf(o, "\n"); + } + + if (encryption->sig_key_id_len > 0) { + fprintf(o, "%sContentSigKeyID : 0x", + indent->indent_str().c_str()); + for (int k = 0; k < encryption->sig_key_id_len; ++k) { + fprintf(o, "%x", encryption->sig_key_id[k]); + } + fprintf(o, "\n"); + } + + fprintf(o, "%sContentSigAlgo : %lld\n", + indent->indent_str().c_str(), encryption->sig_algo); + fprintf(o, "%sContentSigHashAlgo : %lld\n", + indent->indent_str().c_str(), encryption->sig_hash_algo); + + const ContentEncoding::ContentEncAESSettings& aes = + encryption->aes_settings; + fprintf(o, "%sCipherMode : %lld\n", + indent->indent_str().c_str(), aes.cipher_mode); + } + } + + if (track_type == mkvparser::Track::kVideo) { + const mkvparser::VideoTrack* const video_track = + static_cast<const mkvparser::VideoTrack*>(track); + const int64_t width = video_track->GetWidth(); + const int64_t height = video_track->GetHeight(); + const int64_t display_width = video_track->GetDisplayWidth(); + const int64_t display_height = video_track->GetDisplayHeight(); + const int64_t display_unit = video_track->GetDisplayUnit(); + const double frame_rate = video_track->GetFrameRate(); + fprintf(o, "%sPixelWidth : %" PRId64 "\n", indent->indent_str().c_str(), + width); + fprintf(o, "%sPixelHeight : %" PRId64 "\n", indent->indent_str().c_str(), + height); + if (frame_rate > 0.0) + fprintf(o, "%sFrameRate : %g\n", indent->indent_str().c_str(), + video_track->GetFrameRate()); + if (display_unit > 0 || display_width != width || + display_height != height) { + fprintf(o, "%sDisplayWidth : %" PRId64 "\n", + indent->indent_str().c_str(), display_width); + fprintf(o, "%sDisplayHeight : %" PRId64 "\n", + indent->indent_str().c_str(), display_height); + fprintf(o, "%sDisplayUnit : %" PRId64 "\n", + indent->indent_str().c_str(), display_unit); + } + + const mkvparser::Colour* const colour = video_track->GetColour(); + if (colour) { + // TODO(fgalligan): Add support for Colour's address and size. + fprintf(o, "%sColour:\n", indent->indent_str().c_str()); + indent->Adjust(libwebm::kIncreaseIndent); + + const int64_t matrix_coefficients = colour->matrix_coefficients; + const int64_t bits_per_channel = colour->bits_per_channel; + const int64_t chroma_subsampling_horz = colour->chroma_subsampling_horz; + const int64_t chroma_subsampling_vert = colour->chroma_subsampling_vert; + const int64_t cb_subsampling_horz = colour->cb_subsampling_horz; + const int64_t cb_subsampling_vert = colour->cb_subsampling_vert; + const int64_t chroma_siting_horz = colour->chroma_siting_horz; + const int64_t chroma_siting_vert = colour->chroma_siting_vert; + const int64_t range = colour->range; + const int64_t transfer_characteristics = + colour->transfer_characteristics; + const int64_t primaries = colour->primaries; + const int64_t max_cll = colour->max_cll; + const int64_t max_fall = colour->max_fall; + if (matrix_coefficients != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sMatrixCoefficients : %" PRId64 "\n", + indent->indent_str().c_str(), matrix_coefficients); + if (bits_per_channel != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sBitsPerChannel : %" PRId64 "\n", + indent->indent_str().c_str(), bits_per_channel); + if (chroma_subsampling_horz != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sChromaSubsamplingHorz : %" PRId64 "\n", + indent->indent_str().c_str(), chroma_subsampling_horz); + if (chroma_subsampling_vert != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sChromaSubsamplingVert : %" PRId64 "\n", + indent->indent_str().c_str(), chroma_subsampling_vert); + if (cb_subsampling_horz != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sCbSubsamplingHorz : %" PRId64 "\n", + indent->indent_str().c_str(), cb_subsampling_horz); + if (cb_subsampling_vert != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sCbSubsamplingVert : %" PRId64 "\n", + indent->indent_str().c_str(), cb_subsampling_vert); + if (chroma_siting_horz != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sChromaSitingHorz : %" PRId64 "\n", + indent->indent_str().c_str(), chroma_siting_horz); + if (chroma_siting_vert != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sChromaSitingVert : %" PRId64 "\n", + indent->indent_str().c_str(), chroma_siting_vert); + if (range != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sRange : %" PRId64 "\n", + indent->indent_str().c_str(), range); + if (transfer_characteristics != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sTransferCharacteristics : %" PRId64 "\n", + indent->indent_str().c_str(), transfer_characteristics); + if (primaries != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sPrimaries : %" PRId64 "\n", + indent->indent_str().c_str(), primaries); + if (max_cll != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sMaxCLL : %" PRId64 "\n", + indent->indent_str().c_str(), max_cll); + if (max_fall != mkvparser::Colour::kValueNotPresent) + fprintf(o, "%sMaxFALL : %" PRId64 "\n", + indent->indent_str().c_str(), max_fall); + + const mkvparser::MasteringMetadata* const metadata = + colour->mastering_metadata; + if (metadata) { + // TODO(fgalligan): Add support for MasteringMetadata's address and + // size. + fprintf(o, "%sMasteringMetadata:\n", indent->indent_str().c_str()); + indent->Adjust(libwebm::kIncreaseIndent); + + const mkvparser::PrimaryChromaticity* const red = metadata->r; + const mkvparser::PrimaryChromaticity* const green = metadata->g; + const mkvparser::PrimaryChromaticity* const blue = metadata->b; + const mkvparser::PrimaryChromaticity* const white = + metadata->white_point; + const float max = metadata->luminance_max; + const float min = metadata->luminance_min; + if (red) { + fprintf(o, "%sPrimaryRChromaticityX : %g\n", + indent->indent_str().c_str(), red->x); + fprintf(o, "%sPrimaryRChromaticityY : %g\n", + indent->indent_str().c_str(), red->y); + } + if (green) { + fprintf(o, "%sPrimaryGChromaticityX : %g\n", + indent->indent_str().c_str(), green->x); + fprintf(o, "%sPrimaryGChromaticityY : %g\n", + indent->indent_str().c_str(), green->y); + } + if (blue) { + fprintf(o, "%sPrimaryBChromaticityX : %g\n", + indent->indent_str().c_str(), blue->x); + fprintf(o, "%sPrimaryBChromaticityY : %g\n", + indent->indent_str().c_str(), blue->y); + } + if (white) { + fprintf(o, "%sWhitePointChromaticityX : %g\n", + indent->indent_str().c_str(), white->x); + fprintf(o, "%sWhitePointChromaticityY : %g\n", + indent->indent_str().c_str(), white->y); + } + if (max != mkvparser::MasteringMetadata::kValueNotPresent) + fprintf(o, "%sLuminanceMax : %g\n", + indent->indent_str().c_str(), max); + if (min != mkvparser::MasteringMetadata::kValueNotPresent) + fprintf(o, "%sLuminanceMin : %g\n", + indent->indent_str().c_str(), min); + indent->Adjust(libwebm::kDecreaseIndent); + } + indent->Adjust(libwebm::kDecreaseIndent); + } + + const mkvparser::Projection* const projection = + video_track->GetProjection(); + if (projection) { + fprintf(o, "%sProjection:\n", indent->indent_str().c_str()); + indent->Adjust(libwebm::kIncreaseIndent); + + const int projection_type = static_cast<int>(projection->type); + const int kTypeNotPresent = + static_cast<int>(mkvparser::Projection::kTypeNotPresent); + const float kValueNotPresent = mkvparser::Projection::kValueNotPresent; + if (projection_type != kTypeNotPresent) + fprintf(o, "%sProjectionType : %d\n", + indent->indent_str().c_str(), projection_type); + if (projection->private_data) + fprintf(o, "%sProjectionPrivate(size) : %d\n", + indent->indent_str().c_str(), + static_cast<int>(projection->private_data_length)); + if (projection->pose_yaw != kValueNotPresent) + fprintf(o, "%sProjectionPoseYaw : %g\n", + indent->indent_str().c_str(), projection->pose_yaw); + if (projection->pose_pitch != kValueNotPresent) + fprintf(o, "%sProjectionPosePitch : %g\n", + indent->indent_str().c_str(), projection->pose_pitch); + if (projection->pose_roll != kValueNotPresent) + fprintf(o, "%sProjectionPoseRoll : %g\n", + indent->indent_str().c_str(), projection->pose_roll); + indent->Adjust(libwebm::kDecreaseIndent); + } + } else if (track_type == mkvparser::Track::kAudio) { + const mkvparser::AudioTrack* const audio_track = + static_cast<const mkvparser::AudioTrack*>(track); + const int64_t channels = audio_track->GetChannels(); + const int64_t bit_depth = audio_track->GetBitDepth(); + const uint64_t codec_delay = audio_track->GetCodecDelay(); + const uint64_t seek_preroll = audio_track->GetSeekPreRoll(); + fprintf(o, "%sChannels : %" PRId64 "\n", + indent->indent_str().c_str(), channels); + if (bit_depth > 0) + fprintf(o, "%sBitDepth : %" PRId64 "\n", + indent->indent_str().c_str(), bit_depth); + fprintf(o, "%sSamplingFrequency: %g\n", indent->indent_str().c_str(), + audio_track->GetSamplingRate()); + if (codec_delay) + fprintf(o, "%sCodecDelay : %" PRIu64 "\n", + indent->indent_str().c_str(), codec_delay); + if (seek_preroll) + fprintf(o, "%sSeekPreRoll : %" PRIu64 "\n", + indent->indent_str().c_str(), seek_preroll); + } + indent->Adjust(libwebm::kDecreaseIndent * 2); + } + + return true; +} + +// libvpx reference: vp9/vp9_dx_iface.c +void ParseSuperframeIndex(const uint8_t* data, size_t data_sz, + uint32_t sizes[8], int* count) { + const uint8_t marker = data[data_sz - 1]; + *count = 0; + + if ((marker & 0xe0) == 0xc0) { + const int frames = (marker & 0x7) + 1; + const int mag = ((marker >> 3) & 0x3) + 1; + const size_t index_sz = 2 + mag * frames; + + if (data_sz >= index_sz && data[data_sz - index_sz] == marker) { + // found a valid superframe index + const uint8_t* x = data + data_sz - index_sz + 1; + + for (int i = 0; i < frames; ++i) { + uint32_t this_sz = 0; + + for (int j = 0; j < mag; ++j) { + this_sz |= (*x++) << (j * 8); + } + sizes[i] = this_sz; + } + *count = frames; + } + } +} + +void PrintVP9Info(const uint8_t* data, int size, FILE* o, int64_t time_ns, + FrameStats* stats, vp9_parser::Vp9HeaderParser* parser, + vp9_parser::Vp9LevelStats* level_stats) { + if (size < 1) + return; + + uint32_t sizes[8]; + int i = 0, count = 0; + ParseSuperframeIndex(data, size, sizes, &count); + + // Remove all frames that are less than window size. + while (!stats->window.empty() && + stats->window.front() < (time_ns - (kNanosecondsPerSecondi - 1))) + stats->window.pop(); + + do { + const size_t frame_length = (count > 0) ? sizes[i] : size; + if (frame_length > static_cast<size_t>(std::numeric_limits<int>::max()) || + static_cast<int>(frame_length) > size) { + fprintf(o, " invalid VP9 frame size (%u)\n", + static_cast<uint32_t>(frame_length)); + return; + } + if (!parser->ParseUncompressedHeader(data, frame_length)) + return; + level_stats->AddFrame(*parser, time_ns); + + // const int frame_marker = (data[0] >> 6) & 0x3; + const int version = parser->profile(); + const int key = parser->key(); + const int altref_frame = parser->altref(); + const int error_resilient_mode = parser->error_resilient_mode(); + const int row_tiles = parser->row_tiles(); + const int column_tiles = parser->column_tiles(); + const int frame_parallel_mode = parser->frame_parallel_mode(); + + if (key && + !(size >= 4 && data[1] == 0x49 && data[2] == 0x83 && data[3] == 0x42)) { + fprintf(o, " invalid VP9 signature"); + return; + } + + stats->window.push(time_ns); + ++stats->frames; + + if (altref_frame) { + const int delta_altref = stats->frames_since_last_altref; + if (stats->first_altref) { + stats->first_altref = false; + } else if (delta_altref < stats->minimum_altref_distance) { + stats->minimum_altref_distance = delta_altref; + stats->min_altref_end_ns = time_ns; + } + stats->frames_since_last_altref = 0; + } else { + ++stats->frames_since_last_altref; + ++stats->displayed_frames; + } + + if (count > 0) { + fprintf(o, " packed [%d]: {", i); + } + + fprintf(o, " key:%d v:%d altref:%d errm:%d rt:%d ct:%d fpm:%d", key, + version, altref_frame, error_resilient_mode, row_tiles, + column_tiles, frame_parallel_mode); + + if (key && size > 4) { + fprintf(o, " cs:%d", parser->color_space()); + } + + if (count > 0) { + fprintf(o, " size: %u }", sizes[i]); + data += sizes[i]; + size -= sizes[i]; + } + ++i; + } while (i < count); + + if (stats->max_window_size < static_cast<int64_t>(stats->window.size())) { + stats->max_window_size = stats->window.size(); + stats->max_window_end_ns = time_ns; + } +} + +void PrintVP8Info(const uint8_t* data, int size, FILE* o) { + if (size < 3) + return; + + const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16); + const int key = !(bits & 0x1); + const int altref_frame = !((bits >> 4) & 0x1); + const int version = (bits >> 1) & 0x7; + const int partition_length = (bits >> 5) & 0x7FFFF; + if (key && + !(size >= 6 && data[3] == 0x9d && data[4] == 0x01 && data[5] == 0x2a)) { + fprintf(o, " invalid VP8 signature"); + return; + } + fprintf(o, " key:%d v:%d altref:%d partition_length:%d", key, version, + altref_frame, partition_length); +} + +// Prints the partition offsets of the sub-sample encryption. |data| must point +// to an encrypted frame just after the signal byte. Returns the number of +// bytes read from the sub-sample partition information. +int PrintSubSampleEncryption(const uint8_t* data, int size, FILE* o) { + int read_end = sizeof(uint64_t); + + // Skip past IV. + if (size < read_end) + return 0; + data += sizeof(uint64_t); + + // Read number of partitions. + read_end += sizeof(uint8_t); + if (size < read_end) + return 0; + const int num_partitions = data[0]; + data += sizeof(uint8_t); + + // Read partitions. + for (int i = 0; i < num_partitions; ++i) { + read_end += sizeof(uint32_t); + if (size < read_end) + return 0; + uint32_t partition_offset; + memcpy(&partition_offset, data, sizeof(partition_offset)); + partition_offset = libwebm::bigendian_to_host(partition_offset); + fprintf(o, " off[%d]:%u", i, partition_offset); + data += sizeof(uint32_t); + } + + return read_end; +} + +bool OutputCluster(const mkvparser::Cluster& cluster, + const mkvparser::Tracks& tracks, const Options& options, + FILE* o, mkvparser::MkvReader* reader, Indent* indent, + int64_t* clusters_size, FrameStats* stats, + vp9_parser::Vp9HeaderParser* parser, + vp9_parser::Vp9LevelStats* level_stats) { + if (clusters_size) { + // Load the Cluster. + const mkvparser::BlockEntry* block_entry; + long status = cluster.GetFirst(block_entry); + if (status) { + fprintf(stderr, "Could not get first Block of Cluster.\n"); + return false; + } + + *clusters_size += cluster.GetElementSize(); + } + + if (options.output_clusters) { + const int64_t time_ns = cluster.GetTime(); + const int64_t duration_ns = cluster.GetLastTime() - cluster.GetFirstTime(); + + fprintf(o, "%sCluster:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @: %lld", cluster.m_element_start); + if (options.output_size) + fprintf(o, " size: %lld", cluster.GetElementSize()); + fprintf(o, "\n"); + indent->Adjust(libwebm::kIncreaseIndent); + if (options.output_seconds) + fprintf(o, "%sTimecode (sec) : %g\n", indent->indent_str().c_str(), + time_ns / kNanosecondsPerSecond); + else + fprintf(o, "%sTimecode (nano): %" PRId64 "\n", + indent->indent_str().c_str(), time_ns); + if (options.output_seconds) + fprintf(o, "%sDuration (sec) : %g\n", indent->indent_str().c_str(), + duration_ns / kNanosecondsPerSecond); + else + fprintf(o, "%sDuration (nano): %" PRId64 "\n", + indent->indent_str().c_str(), duration_ns); + + fprintf(o, "%s# Blocks : %ld\n", indent->indent_str().c_str(), + cluster.GetEntryCount()); + } + + if (options.output_blocks) { + const mkvparser::BlockEntry* block_entry; + long status = cluster.GetFirst(block_entry); + if (status) { + fprintf(stderr, "Could not get first Block of Cluster.\n"); + return false; + } + + std::vector<unsigned char> vector_data; + while (block_entry != NULL && !block_entry->EOS()) { + const mkvparser::Block* const block = block_entry->GetBlock(); + if (!block) { + fprintf(stderr, "Could not getblock entry.\n"); + return false; + } + + const unsigned int track_number = + static_cast<unsigned int>(block->GetTrackNumber()); + const mkvparser::Track* track = tracks.GetTrackByNumber(track_number); + if (!track) { + fprintf(stderr, "Could not get Track.\n"); + return false; + } + + const int64_t track_type = track->GetType(); + if ((track_type == mkvparser::Track::kVideo && options.output_video) || + (track_type == mkvparser::Track::kAudio && options.output_audio)) { + const int64_t time_ns = block->GetTime(&cluster); + const bool is_key = block->IsKey(); + + if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) { + fprintf(o, "%sBlockGroup:\n", indent->indent_str().c_str()); + indent->Adjust(libwebm::kIncreaseIndent); + } + + fprintf(o, "%sBlock: type:%s frame:%s", indent->indent_str().c_str(), + track_type == mkvparser::Track::kVideo ? "V" : "A", + is_key ? "I" : "P"); + if (options.output_seconds) + fprintf(o, " secs:%5g", time_ns / kNanosecondsPerSecond); + else + fprintf(o, " nano:%10" PRId64, time_ns); + + if (options.output_offset) + fprintf(o, " @_payload: %lld", block->m_start); + if (options.output_size) + fprintf(o, " size_payload: %lld", block->m_size); + + const uint8_t KEncryptedBit = 0x1; + const uint8_t kSubSampleBit = 0x2; + const int kSignalByteSize = 1; + bool encrypted_stream = false; + if (options.output_encrypted_info) { + if (track->GetContentEncodingCount() > 0) { + // Only check the first content encoding. + const ContentEncoding* const encoding = + track->GetContentEncodingByIndex(0); + if (encoding) { + if (encoding->GetEncryptionCount() > 0) { + const ContentEncoding::ContentEncryption* const encryption = + encoding->GetEncryptionByIndex(0); + if (encryption) { + const ContentEncoding::ContentEncAESSettings& aes = + encryption->aes_settings; + if (aes.cipher_mode == 1) { + encrypted_stream = true; + } + } + } + } + } + + if (encrypted_stream) { + const mkvparser::Block::Frame& frame = block->GetFrame(0); + if (frame.len > static_cast<int>(vector_data.size())) { + vector_data.resize(frame.len + 1024); + } + + unsigned char* data = &vector_data[0]; + if (frame.Read(reader, data) < 0) { + fprintf(stderr, "Could not read frame.\n"); + return false; + } + + const bool encrypted_frame = !!(data[0] & KEncryptedBit); + const bool sub_sample_encrypt = !!(data[0] & kSubSampleBit); + fprintf(o, " enc: %d", encrypted_frame ? 1 : 0); + fprintf(o, " sub: %d", sub_sample_encrypt ? 1 : 0); + + if (encrypted_frame) { + uint64_t iv; + memcpy(&iv, data + kSignalByteSize, sizeof(iv)); + fprintf(o, " iv: %" PRIx64, iv); + } + } + } + + if (options.output_codec_info) { + const int frame_count = block->GetFrameCount(); + + if (frame_count > 1) { + fprintf(o, "\n"); + indent->Adjust(libwebm::kIncreaseIndent); + } + + for (int i = 0; i < frame_count; ++i) { + if (track_type == mkvparser::Track::kVideo) { + const mkvparser::Block::Frame& frame = block->GetFrame(i); + if (frame.len > static_cast<int>(vector_data.size())) { + vector_data.resize(frame.len + 1024); + } + + unsigned char* data = &vector_data[0]; + if (frame.Read(reader, data) < 0) { + fprintf(stderr, "Could not read frame.\n"); + return false; + } + + if (frame_count > 1) + fprintf(o, "\n%sVP8 data :", indent->indent_str().c_str()); + + bool encrypted_frame = false; + bool sub_sample_encrypt = false; + int frame_size = static_cast<int>(frame.len); + + int frame_offset = 0; + if (encrypted_stream) { + if (data[0] & KEncryptedBit) { + encrypted_frame = true; + if (data[0] & kSubSampleBit) { + sub_sample_encrypt = true; + data += kSignalByteSize; + frame_size -= kSignalByteSize; + frame_offset = + PrintSubSampleEncryption(data, frame_size, o); + } + } else { + frame_offset = kSignalByteSize; + } + } + + if (!encrypted_frame || sub_sample_encrypt) { + data += frame_offset; + frame_size -= frame_offset; + + const string codec_id = ToString(track->GetCodecId()); + if (codec_id == "V_VP8") { + PrintVP8Info(data, frame_size, o); + } else if (codec_id == "V_VP9") { + PrintVP9Info(data, frame_size, o, time_ns, stats, parser, + level_stats); + } + } + } + } + + if (frame_count > 1) + indent->Adjust(libwebm::kDecreaseIndent); + } + + if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) { + const int64_t discard_padding = block->GetDiscardPadding(); + if (discard_padding != 0) { + fprintf(o, "\n%sDiscardPadding: %10" PRId64, + indent->indent_str().c_str(), discard_padding); + } + indent->Adjust(libwebm::kDecreaseIndent); + } + + fprintf(o, "\n"); + } + + status = cluster.GetNext(block_entry, block_entry); + if (status) { + printf("\n Could not get next block of cluster.\n"); + return false; + } + } + } + + if (options.output_clusters) + indent->Adjust(libwebm::kDecreaseIndent); + + return true; +} + +bool OutputCues(const mkvparser::Segment& segment, + const mkvparser::Tracks& tracks, const Options& options, + FILE* o, Indent* indent) { + const mkvparser::Cues* const cues = segment.GetCues(); + if (cues == NULL) + return true; + + // Load all of the cue points. + while (!cues->DoneParsing()) + cues->LoadCuePoint(); + + // Confirm that the input has cue points. + const mkvparser::CuePoint* const first_cue = cues->GetFirst(); + if (first_cue == NULL) { + fprintf(o, "%sNo cue points.\n", indent->indent_str().c_str()); + return true; + } + + // Input has cue points, dump them: + fprintf(o, "%sCues:", indent->indent_str().c_str()); + if (options.output_offset) + fprintf(o, " @:%lld", cues->m_element_start); + if (options.output_size) + fprintf(o, " size:%lld", cues->m_element_size); + fprintf(o, "\n"); + + const mkvparser::CuePoint* cue_point = first_cue; + int cue_point_num = 1; + const int num_tracks = static_cast<int>(tracks.GetTracksCount()); + indent->Adjust(libwebm::kIncreaseIndent); + + do { + for (int track_num = 0; track_num < num_tracks; ++track_num) { + const mkvparser::Track* const track = tracks.GetTrackByIndex(track_num); + const mkvparser::CuePoint::TrackPosition* const track_pos = + cue_point->Find(track); + + if (track_pos != NULL) { + const char track_type = + (track->GetType() == mkvparser::Track::kVideo) ? 'V' : 'A'; + fprintf(o, "%sCue Point:%d type:%c track:%d", + indent->indent_str().c_str(), cue_point_num, track_type, + static_cast<int>(track->GetNumber())); + + if (options.output_seconds) { + fprintf(o, " secs:%g", + cue_point->GetTime(&segment) / kNanosecondsPerSecond); + } else { + fprintf(o, " nano:%lld", cue_point->GetTime(&segment)); + } + + if (options.output_blocks) + fprintf(o, " block:%lld", track_pos->m_block); + + if (options.output_offset) + fprintf(o, " @:%lld", track_pos->m_pos); + + fprintf(o, "\n"); + } + } + + cue_point = cues->GetNext(cue_point); + ++cue_point_num; + } while (cue_point != NULL); + + indent->Adjust(libwebm::kDecreaseIndent); + return true; +} + +} // namespace + +int main(int argc, char* argv[]) { + string input; + Options options; + + const int argc_check = argc - 1; + for (int i = 1; i < argc; ++i) { + if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) { + Usage(); + return EXIT_SUCCESS; + } else if (!strcmp("-v", argv[i])) { + printf("version: %s\n", VERSION_STRING); + } else if (!strcmp("-i", argv[i]) && i < argc_check) { + input = argv[++i]; + } else if (!strcmp("-all", argv[i])) { + options.SetAll(true); + } else if (Options::MatchesBooleanOption("video", argv[i])) { + options.output_video = !strcmp("-video", argv[i]); + } else if (Options::MatchesBooleanOption("audio", argv[i])) { + options.output_audio = !strcmp("-audio", argv[i]); + } else if (Options::MatchesBooleanOption("size", argv[i])) { + options.output_size = !strcmp("-size", argv[i]); + } else if (Options::MatchesBooleanOption("offset", argv[i])) { + options.output_offset = !strcmp("-offset", argv[i]); + } else if (Options::MatchesBooleanOption("times_seconds", argv[i])) { + options.output_seconds = !strcmp("-times_seconds", argv[i]); + } else if (Options::MatchesBooleanOption("ebml_header", argv[i])) { + options.output_ebml_header = !strcmp("-ebml_header", argv[i]); + } else if (Options::MatchesBooleanOption("segment", argv[i])) { + options.output_segment = !strcmp("-segment", argv[i]); + } else if (Options::MatchesBooleanOption("seekhead", argv[i])) { + options.output_seekhead = !strcmp("-seekhead", argv[i]); + } else if (Options::MatchesBooleanOption("segment_info", argv[i])) { + options.output_segment_info = !strcmp("-segment_info", argv[i]); + } else if (Options::MatchesBooleanOption("tracks", argv[i])) { + options.output_tracks = !strcmp("-tracks", argv[i]); + } else if (Options::MatchesBooleanOption("clusters", argv[i])) { + options.output_clusters = !strcmp("-clusters", argv[i]); + } else if (Options::MatchesBooleanOption("blocks", argv[i])) { + options.output_blocks = !strcmp("-blocks", argv[i]); + } else if (Options::MatchesBooleanOption("codec_info", argv[i])) { + options.output_codec_info = !strcmp("-codec_info", argv[i]); + } else if (Options::MatchesBooleanOption("clusters_size", argv[i])) { + options.output_clusters_size = !strcmp("-clusters_size", argv[i]); + } else if (Options::MatchesBooleanOption("encrypted_info", argv[i])) { + options.output_encrypted_info = !strcmp("-encrypted_info", argv[i]); + } else if (Options::MatchesBooleanOption("cues", argv[i])) { + options.output_cues = !strcmp("-cues", argv[i]); + } else if (Options::MatchesBooleanOption("frame_stats", argv[i])) { + options.output_frame_stats = !strcmp("-frame_stats", argv[i]); + } else if (Options::MatchesBooleanOption("vp9_level", argv[i])) { + options.output_vp9_level = !strcmp("-vp9_level", argv[i]); + } + } + + if (argc < 3 || input.empty()) { + Usage(); + return EXIT_FAILURE; + } + + std::unique_ptr<mkvparser::MkvReader> reader( + new (std::nothrow) mkvparser::MkvReader()); // NOLINT + if (reader->Open(input.c_str())) { + fprintf(stderr, "Error opening file:%s\n", input.c_str()); + return EXIT_FAILURE; + } + + long long int pos = 0; + std::unique_ptr<mkvparser::EBMLHeader> ebml_header( + new (std::nothrow) mkvparser::EBMLHeader()); // NOLINT + if (ebml_header->Parse(reader.get(), pos) < 0) { + fprintf(stderr, "Error parsing EBML header.\n"); + return EXIT_FAILURE; + } + + Indent indent(0); + FILE* out = stdout; + + if (options.output_ebml_header) + OutputEBMLHeader(*ebml_header.get(), out, &indent); + + mkvparser::Segment* temp_segment; + if (mkvparser::Segment::CreateInstance(reader.get(), pos, temp_segment)) { + fprintf(stderr, "Segment::CreateInstance() failed.\n"); + return EXIT_FAILURE; + } + std::unique_ptr<mkvparser::Segment> segment(temp_segment); + + if (segment->Load() < 0) { + fprintf(stderr, "Segment::Load() failed.\n"); + return EXIT_FAILURE; + } + + if (options.output_segment) { + OutputSegment(*(segment.get()), options, out); + indent.Adjust(libwebm::kIncreaseIndent); + } + + if (options.output_seekhead) + if (!OutputSeekHead(*(segment.get()), options, out, &indent)) + return EXIT_FAILURE; + + if (options.output_segment_info) + if (!OutputSegmentInfo(*(segment.get()), options, out, &indent)) + return EXIT_FAILURE; + + if (options.output_tracks) + if (!OutputTracks(*(segment.get()), options, out, &indent)) + return EXIT_FAILURE; + + const mkvparser::Tracks* const tracks = segment->GetTracks(); + if (!tracks) { + fprintf(stderr, "Could not get Tracks.\n"); + return EXIT_FAILURE; + } + + // If Cues are before the clusters output them first. + if (options.output_cues) { + const mkvparser::Cluster* cluster = segment->GetFirst(); + const mkvparser::Cues* const cues = segment->GetCues(); + if (cluster != NULL && cues != NULL) { + if (cues->m_element_start < cluster->m_element_start) { + if (!OutputCues(*segment, *tracks, options, out, &indent)) { + return EXIT_FAILURE; + } + options.output_cues = false; + } + } + } + + if (options.output_clusters) + fprintf(out, "%sClusters (count):%ld\n", indent.indent_str().c_str(), + segment->GetCount()); + + int64_t clusters_size = 0; + FrameStats stats; + vp9_parser::Vp9HeaderParser parser; + vp9_parser::Vp9LevelStats level_stats; + const mkvparser::Cluster* cluster = segment->GetFirst(); + while (cluster != NULL && !cluster->EOS()) { + if (!OutputCluster(*cluster, *tracks, options, out, reader.get(), &indent, + &clusters_size, &stats, &parser, &level_stats)) + return EXIT_FAILURE; + cluster = segment->GetNext(cluster); + } + + if (options.output_clusters_size) + fprintf(out, "%sClusters (size):%" PRId64 "\n", indent.indent_str().c_str(), + clusters_size); + + if (options.output_cues) + if (!OutputCues(*segment, *tracks, options, out, &indent)) + return EXIT_FAILURE; + + // TODO(fgalligan): Add support for VP8. + if (options.output_frame_stats && + stats.minimum_altref_distance != std::numeric_limits<int>::max()) { + const double actual_fps = + stats.frames / + (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond); + const double displayed_fps = + stats.displayed_frames / + (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond); + fprintf(out, "\nActual fps:%g Displayed fps:%g\n", actual_fps, + displayed_fps); + + fprintf(out, "Minimum Altref Distance:%d at:%g seconds\n", + stats.minimum_altref_distance, + stats.min_altref_end_ns / kNanosecondsPerSecond); + + // TODO(fgalligan): Add support for window duration other than 1 second. + const double sec_end = stats.max_window_end_ns / kNanosecondsPerSecond; + const double sec_start = + stats.max_window_end_ns > kNanosecondsPerSecondi ? sec_end - 1.0 : 0.0; + fprintf(out, "Maximum Window:%g-%g seconds Window fps:%" PRId64 "\n", + sec_start, sec_end, stats.max_window_size); + } + + if (options.output_vp9_level) { + level_stats.set_duration(segment->GetInfo()->GetDuration()); + const vp9_parser::Vp9Level level = level_stats.GetLevel(); + fprintf(out, "VP9 Level:%d\n", level); + fprintf( + out, + "mlsr:%" PRId64 " mlps:%" PRId64 " mlpb:%" PRId64 + " abr:%g mcs:%g cr:%g mct:%d" + " mad:%d mrf:%d\n", + level_stats.GetMaxLumaSampleRate(), level_stats.GetMaxLumaPictureSize(), + level_stats.GetMaxLumaPictureBreadth(), level_stats.GetAverageBitRate(), + level_stats.GetMaxCpbSize(), level_stats.GetCompressionRatio(), + level_stats.GetMaxColumnTiles(), level_stats.GetMinimumAltrefDistance(), + level_stats.GetMaxReferenceFrames()); + } + return EXIT_SUCCESS; +} diff --git a/webm_parser/README.md b/webm_parser/README.md new file mode 100644 index 0000000..f3d37ff --- /dev/null +++ b/webm_parser/README.md @@ -0,0 +1,325 @@ +# WebM Parser {#mainpage} + +# Introduction + +This WebM parser is a C++11-based parser that aims to be a safe and complete +parser for WebM. It supports all WebM elements (from the old deprecated ones to +the newest ones like `Colour`), including recursive elements like `ChapterAtom` +and `SimpleTag`. It supports incremental parsing; parsing may be stopped at any +point and resumed later as needed. It also supports starting at an arbitrary +WebM element, so parsing need not start from the beginning of the file. + +The parser (`WebmParser`) works by being fed input data from a data source (an +instance of `Reader`) that represents a WebM file. The parser will parse the +WebM data into various data structures that represent the encoded WebM elements, +and then call corresponding `Callback` event methods as the data structures are +parsed. + +# Building + +CMake support has been added to the root libwebm `CMakeLists.txt` file. Simply +enable the `ENABLE_WEBM_PARSER` feature if using the interactive CMake builder, +or alternatively pass the `-DENABLE_WEBM_PARSER:BOOL=ON` flag from the command +line. By default, this parser is not enabled when building libwebm, so you must +explicitly enable it. + +Alternatively, the following illustrates the minimal commands necessary to +compile the code into a static library without CMake: + +```.sh +c++ -Iinclude -I. -std=c++11 -c src/*.cc +ar rcs libwebm.a *.o +``` + +# Using the parser + +There are 3 basic components in the parser that are used: `Reader`, `Callback`, +and `WebmParser`. + +## `Reader` + +The `Reader` interface acts as a data source for the parser. You may subclass it +and implement your own data source if you wish. Alternatively, use the +`FileReader`, `IstreamReader`, or `BufferReader` if you wish to read from a +`FILE*`, `std::istream`, or `std::vector<std::uint8_t>`, respectively. + +The parser supports `Reader` implementations that do short reads. If +`Reader::Skip()` or `Reader::Read()` do a partial read (returning +`Status::kOkPartial`), the parser will call them again in an attempt to read +more data. If no data is available, the `Reader` may return some other status +(like `Status::kWouldBlock`) to indicate that no data is available. In this +situation, the parser will stop parsing and return the status it received. +Parsing may be resumed later when more data is available. + +When the `Reader` has reached the end of the WebM document and no more data is +available, it should return `Status::kEndOfFile`. This will cause parsing to +stop. If the file ends at a valid location (that is, there aren't any elements +that have specified a size that indicates the file ended prematurely), the +parser will translate `Status::kEndOfFile` into `Status::kOkCompleted` and +return it. If the file ends prematurely, the parser will return +`Status::kEndOfFile` to indicate that. + +Note that if the WebM file contains elements that have an unknown size (or a +seek has been performed and the parser doesn't know the size of the root +element(s)), and the parser is parsing them and hits end-of-file, the parser may +still call `Reader::Read()`/`Reader::Skip()` multiple times (even though they've +already reported `Status::kEndOfFile`) as nested parsers terminate parsing. +Because of this, `Reader::Read()`/`Reader::Skip()` implementations should be +able to handle being called multiple times after the file's end has been +reached, and they should consistently return `Status::kEndOfFile`. + +The three provided readers (`FileReader`, `IstreamReader`, and `BufferReader`) +are blocking implementations (they won't return `Status::kWouldBlock`), so if +you're using them the parser will run until it entirely consumes all their data +(unless, of course, you request the parser to stop via `Callback`... see the +next section). + +## `Callback` + +As the parser progresses through the file, it builds objects (see +`webm/dom_types.h`) that represent parsed data structures. The parser then +notifies the `Callback` implementation as objects complete parsing. For some +data structures (like frames or Void elements), the parser notifies the +`Callback` and requests it to consume the data directly from the `Reader` (this +is done for structures that can be large/frequent binary blobs in order to allow +you to read the data directly into the object/type of your choice, rather than +just reading them into a `std::vector<std::uint8_t>` and making you copy it into +a different object if you wanted to work with something other than +`std::vector<std::uint8_t>`). + +The parser was designed to parse the data into objects that are small enough +that the `Callback` can be quickly and frequently notified as soon as the object +is ready, but large enough that the objects received by the `Callback` are still +useful. Having `Callback` events for every tiny integer/float/string/etc. +element would require too much assembly and work to be useful to most users, and +pasing the file into a single DOM tree (or a small handful of large conglomerate +structures) would unnecessarily delay video playback or consume too much memory +on smaller devices. + +The parser may call the following methods while nearly anywhere in the file: + +- `Callback::OnElementBegin()`: This is called for every element that the + parser encounters. This is primarily useful if you want to skip some + elements or build a map of every element in the file. +- `Callback::OnUnknownElement()`: This is called when an element is either not + a valid/recognized WebM element, or it is a WebM element but is improperly + nested (e.g. an EBMLVersion element inside of a Segment element). The parser + doesn't know how to handle the element; it could just skip it but instead + defers to the `Callback` to decide how it should be handled. The default + implementation just skips the element. +- `Callback::OnVoid()`: Void elements can appear anywhere in any master + element. This method will be called to handle the Void element. + +The parser may call the following methods in the proper nesting order, as shown +in the list. A `*Begin()` method will always be matched up with its +corresponding `*End()` method (unless a seek has been performed). The parser +will only call the methods in the proper nesting order as specified in the WebM +DOM. For example, `Callback::OnEbml()` will never be called in between +`Callback::OnSegmentBegin()`/`Callback::OnSegmentEnd()` (since the EBML element +is not a child of the Segment element), and `Callback::OnTrackEntry()` will only +ever be called in between +`Callback::OnSegmentBegin()`/`Callback::OnSegmentEnd()` (since the TrackEntry +element is a (grand-)child of the Segment element and must be contained by a +Segment element). `Callback::OnFrame()` is listed twice because it will be +called to handle frames contained in both SimpleBlock and Block elements. + +- `Callback::OnEbml()` +- `Callback::OnSegmentBegin()` + - `Callback::OnSeek()` + - `Callback::OnInfo()` + - `Callback::OnClusterBegin()` + - `Callback::OnSimpleBlockBegin()` + - `Callback::OnFrame()` + - `Callback::OnSimpleBlockEnd()` + - `Callback::OnBlockGroupBegin()` + - `Callback::OnBlockBegin()` + - `Callback::OnFrame()` + - `Callback::OnBlockEnd()` + - `Callback::OnBlockGroupEnd()` + - `Callback::OnClusterEnd()` + - `Callback::OnTrackEntry()` + - `Callback::OnCuePoint()` + - `Callback::OnEditionEntry()` + - `Callback::OnTag()` +- `Callback::OnSegmentEnd()` + +Only `Callback::OnFrame()` (and no other `Callback` methods) will be called in +between `Callback::OnSimpleBlockBegin()`/`Callback::OnSimpleBlockEnd()` or +`Callback::OnBlockBegin()`/`Callback::OnBlockEnd()`, since the SimpleBlock and +Block elements are not master elements only contain frames. + +Note that seeking into the middle of the file may cause the parser to skip some +`*Begin()` methods. For example, if a seek is performed to a SimpleBlock +element, `Callback::OnSegmentBegin()` and `Callback::OnClusterBegin()` will not +be called. In this situation, the full sequence of callback events would be +(assuming the file ended after the SimpleBlock): +`Callback::OnSimpleBlockBegin()`, `Callback::OnFrame()` (for every frame in the +SimpleBlock), `Callback::OnSimpleBlockEnd()`, `Callback::OnClusterEnd()`, and +`Callback::OnSegmentEnd()`. Since the Cluster and Segment elements were skipped, +the `Cluster` DOM object may have some members marked as absent, and the +`*End()` events for the Cluster and Segment elements will have metadata with +unknown header position, header length, and body size (see `kUnknownHeaderSize`, +`kUnknownElementSize`, and `kUnknownElementPosition`). + +When a `Callback` method has completed, it should return `Status::kOkCompleted` +to allow parsing to continue. If you would like parsing to stop, return any +other status code (except `Status::kEndOfFile`, since that's treated somewhat +specially and is intended for `Reader`s to use), which the parser will return. +If you return a non-parsing-error status code (.e.g. `Status::kOkPartial`, +`Status::kWouldBlock`, etc. or your own status code with a value > 0), parsing +may be resumed again. When parsing is resumed, the parser will call the same +callback method again (and once again, you may return `Status::kOkCompleted` to +let parsing continue or some other value to stop parsing). + +You may subclass the `Callback` element and override methods which you are +interested in receiving events for. By default, methods taking an `Action` +parameter will set it to `Action::kRead` so the entire file is parsed. The +`Callback::OnFrame()` method will just skip over the frame bytes by default. + +## `WebmParser` + +The actual parsing work is done with `WebmParser`. Simply construct a +`WebmParser` and call `WebmParser::Feed()` (providing it a `Callback` and +`Reader` instance) to parse a file. It will return `Status::kOkCompleted` when +the entire file has been successfully parsed. `WebmParser::Feed()` doesn't store +any internal references to the `Callback` or `Reader`. + +If you wish to start parsing from the middle of a file, call +`WebmParser::DidSeek()` before calling `WebmParser::Feed()` to prepare the +parser to receive data starting at an arbitrary point in the file. When seeking, +you should seek to the beginning of a WebM element; seeking to a location that +is not the start of a WebM element (e.g. seeking to a frame, rather than its +containing SimpleBlock/Block element) will cause parsing to fail. Calling +`WebmParser::DidSeek()` will reset the state of the parser and clear any +internal errors, so a `WebmParser` instance may be reused (even if it has +previously failed to parse a file). + +## Building your program + +The following program is a small program that completely parses a file from +stdin: + +```.cc +#include <webm/callback.h> +#include <webm/file_reader.h> +#include <webm/webm_parser.h> + +int main() { + webm::Callback callback; + webm::FileReader reader(std::freopen(nullptr, "rb", stdin)); + webm::WebmParser parser; + parser.Feed(&callback, &reader); +} +``` + +It completely parses the input file, but we need to make a new class that +derives from `Callback` if we want to receive any parsing events. So if we +change it to: + +```.cc +#include <iomanip> +#include <iostream> + +#include <webm/callback.h> +#include <webm/file_reader.h> +#include <webm/status.h> +#include <webm/webm_parser.h> + +class MyCallback : public webm::Callback { + public: + webm::Status OnElementBegin(const webm::ElementMetadata& metadata, + webm::Action* action) override { + std::cout << "Element ID = 0x" + << std::hex << static_cast<std::uint32_t>(metadata.id); + std::cout << std::dec; // Reset to decimal mode. + std::cout << " at position "; + if (metadata.position == webm::kUnknownElementPosition) { + // The position will only be unknown if we've done a seek. But since we + // aren't seeking in this demo, this will never be the case. However, this + // if-statement is included for completeness. + std::cout << "<unknown>"; + } else { + std::cout << metadata.position; + } + std::cout << " with header size "; + if (metadata.header_size == webm::kUnknownHeaderSize) { + // The header size will only be unknown if we've done a seek. But since we + // aren't seeking in this demo, this will never be the case. However, this + // if-statement is included for completeness. + std::cout << "<unknown>"; + } else { + std::cout << metadata.header_size; + } + std::cout << " and body size "; + if (metadata.size == webm::kUnknownElementSize) { + // WebM master elements may have an unknown size, though this is rare. + std::cout << "<unknown>"; + } else { + std::cout << metadata.size; + } + std::cout << '\n'; + + *action = webm::Action::kRead; + return webm::Status(webm::Status::kOkCompleted); + } +}; + +int main() { + MyCallback callback; + webm::FileReader reader(std::freopen(nullptr, "rb", stdin)); + webm::WebmParser parser; + webm::Status status = parser.Feed(&callback, &reader); + if (status.completed_ok()) { + std::cout << "Parsing successfully completed\n"; + } else { + std::cout << "Parsing failed with status code: " << status.code << '\n'; + } +} +``` + +This will output information about every element in the entire file: it's ID, +position, header size, and body size. The status of the parse is also checked +and reported. + +For a more complete example, see `demo/demo.cc`, which parses an entire file and +prints out all of its information. That example overrides every `Callback` +method to show exactly what information is available while parsing and how to +access it. The example is verbose, but that's primarily due to pretty-printing +and string formatting operations. + +When compiling your program, add the `include` directory to your compiler's +header search paths and link to the compiled library. Be sure your compiler has +C++11 mode enabled (`-std=c++11` in clang++ or g++). + +# Testing + +Unit tests are located in the `tests` directory. Google Test and Google Mock are +used as testing frameworks. Building and running the tests will be supported in +the upcoming CMake scripts, but they can currently be built and run by manually +compiling them (and linking to Google Test and Google Mock). + +# Fuzzing + +The parser has been fuzzed with [AFL](http://lcamtuf.coredump.cx/afl/) and +[libFuzzer](http://llvm.org/docs/LibFuzzer.html). If you wish to fuzz the parser +with AFL or libFuzzer but don't want to write an executable that exercises the +parsing API, you may use `fuzzing/webm_fuzzer.cc`. + +When compiling for fuzzing, define the macro +`WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT` to be some integer in order to limit the +maximum size of ASCII/UTF-8/binary elements. It's too easy for the fuzzer to +generate elements that claim to have a ridiculously massive size, which will +cause allocations to fail or the program to allocate too much memory. AFL will +terminate the process if it allocates too much memory (by default, 50 MB), and +the [Address Sanitizer doesn't throw `std::bad_alloc` when an allocation fails] +(https://github.com/google/sanitizers/issues/295). Defining +`WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT` to a low number (say, 1024) will cause the +ASCII/UTF-8/binary element parsers to return `Status::kNotEnoughMemory` if the +element's size exceeds `WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT`, which will avoid +false positives when fuzzing. The parser expects `std::string` and `std::vector` +to throw `std::bad_alloc` when an allocation fails, which doesn't necessarily +happen due to the fuzzers' limitations. + +You may also define the macro `WEBM_FUZZER_SEEK_FIRST` to have +`fuzzing/webm_fuzzer.cc` call `WebmParser::DidSeek()` before doing any parsing. +This will test the seeking code paths. diff --git a/webm_parser/demo/demo.cc b/webm_parser/demo/demo.cc new file mode 100644 index 0000000..47ecbb0 --- /dev/null +++ b/webm_parser/demo/demo.cc @@ -0,0 +1,1161 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include <cstdio> +#include <cstdlib> +#include <iomanip> +#include <iostream> + +#include "webm/callback.h" +#include "webm/file_reader.h" +#include "webm/status.h" +#include "webm/webm_parser.h" + +// We use pretty much everything in the webm namespace. Just pull +// it all in. +using namespace webm; // NOLINT + +template <typename T> +std::ostream& PrintUnknownEnumValue(std::ostream& os, T value) { + return os << std::to_string(static_cast<std::uint64_t>(value)) << " (?)"; +} + +// Overloads for operator<< for pretty printing enums. +std::ostream& operator<<(std::ostream& os, Id id) { + switch (id) { + case Id::kEbml: + return os << "EBML"; + case Id::kEbmlVersion: + return os << "EBMLVersion"; + case Id::kEbmlReadVersion: + return os << "EBMLReadVersion"; + case Id::kEbmlMaxIdLength: + return os << "EBMLMaxIDLength"; + case Id::kEbmlMaxSizeLength: + return os << "EBMLMaxSizeLength"; + case Id::kDocType: + return os << "DocType"; + case Id::kDocTypeVersion: + return os << "DocTypeVersion"; + case Id::kDocTypeReadVersion: + return os << "DocTypeReadVersion"; + case Id::kVoid: + return os << "Void"; + case Id::kSegment: + return os << "Segment"; + case Id::kSeekHead: + return os << "SeekHead"; + case Id::kSeek: + return os << "Seek"; + case Id::kSeekId: + return os << "SeekID"; + case Id::kSeekPosition: + return os << "SeekPosition"; + case Id::kInfo: + return os << "Info"; + case Id::kTimecodeScale: + return os << "TimecodeScale"; + case Id::kDuration: + return os << "Duration"; + case Id::kDateUtc: + return os << "DateUTC"; + case Id::kTitle: + return os << "Title"; + case Id::kMuxingApp: + return os << "MuxingApp"; + case Id::kWritingApp: + return os << "WritingApp"; + case Id::kCluster: + return os << "Cluster"; + case Id::kTimecode: + return os << "Timecode"; + case Id::kPrevSize: + return os << "PrevSize"; + case Id::kSimpleBlock: + return os << "SimpleBlock"; + case Id::kBlockGroup: + return os << "BlockGroup"; + case Id::kBlock: + return os << "Block"; + case Id::kBlockVirtual: + return os << "BlockVirtual"; + case Id::kBlockAdditions: + return os << "BlockAdditions"; + case Id::kBlockMore: + return os << "BlockMore"; + case Id::kBlockAddId: + return os << "BlockAddID"; + case Id::kBlockAdditional: + return os << "BlockAdditional"; + case Id::kBlockDuration: + return os << "BlockDuration"; + case Id::kReferenceBlock: + return os << "ReferenceBlock"; + case Id::kDiscardPadding: + return os << "DiscardPadding"; + case Id::kSlices: + return os << "Slices"; + case Id::kTimeSlice: + return os << "TimeSlice"; + case Id::kLaceNumber: + return os << "LaceNumber"; + case Id::kTracks: + return os << "Tracks"; + case Id::kTrackEntry: + return os << "TrackEntry"; + case Id::kTrackNumber: + return os << "TrackNumber"; + case Id::kTrackUid: + return os << "TrackUID"; + case Id::kTrackType: + return os << "TrackType"; + case Id::kFlagEnabled: + return os << "FlagEnabled"; + case Id::kFlagDefault: + return os << "FlagDefault"; + case Id::kFlagForced: + return os << "FlagForced"; + case Id::kFlagLacing: + return os << "FlagLacing"; + case Id::kDefaultDuration: + return os << "DefaultDuration"; + case Id::kName: + return os << "Name"; + case Id::kLanguage: + return os << "Language"; + case Id::kCodecId: + return os << "CodecID"; + case Id::kCodecPrivate: + return os << "CodecPrivate"; + case Id::kCodecName: + return os << "CodecName"; + case Id::kCodecDelay: + return os << "CodecDelay"; + case Id::kSeekPreRoll: + return os << "SeekPreRoll"; + case Id::kVideo: + return os << "Video"; + case Id::kFlagInterlaced: + return os << "FlagInterlaced"; + case Id::kStereoMode: + return os << "StereoMode"; + case Id::kAlphaMode: + return os << "AlphaMode"; + case Id::kPixelWidth: + return os << "PixelWidth"; + case Id::kPixelHeight: + return os << "PixelHeight"; + case Id::kPixelCropBottom: + return os << "PixelCropBottom"; + case Id::kPixelCropTop: + return os << "PixelCropTop"; + case Id::kPixelCropLeft: + return os << "PixelCropLeft"; + case Id::kPixelCropRight: + return os << "PixelCropRight"; + case Id::kDisplayWidth: + return os << "DisplayWidth"; + case Id::kDisplayHeight: + return os << "DisplayHeight"; + case Id::kDisplayUnit: + return os << "DisplayUnit"; + case Id::kAspectRatioType: + return os << "AspectRatioType"; + case Id::kFrameRate: + return os << "FrameRate"; + case Id::kColour: + return os << "Colour"; + case Id::kMatrixCoefficients: + return os << "MatrixCoefficients"; + case Id::kBitsPerChannel: + return os << "BitsPerChannel"; + case Id::kChromaSubsamplingHorz: + return os << "ChromaSubsamplingHorz"; + case Id::kChromaSubsamplingVert: + return os << "ChromaSubsamplingVert"; + case Id::kCbSubsamplingHorz: + return os << "CbSubsamplingHorz"; + case Id::kCbSubsamplingVert: + return os << "CbSubsamplingVert"; + case Id::kChromaSitingHorz: + return os << "ChromaSitingHorz"; + case Id::kChromaSitingVert: + return os << "ChromaSitingVert"; + case Id::kRange: + return os << "Range"; + case Id::kTransferCharacteristics: + return os << "TransferCharacteristics"; + case Id::kPrimaries: + return os << "Primaries"; + case Id::kMaxCll: + return os << "MaxCLL"; + case Id::kMaxFall: + return os << "MaxFALL"; + case Id::kMasteringMetadata: + return os << "MasteringMetadata"; + case Id::kPrimaryRChromaticityX: + return os << "PrimaryRChromaticityX"; + case Id::kPrimaryRChromaticityY: + return os << "PrimaryRChromaticityY"; + case Id::kPrimaryGChromaticityX: + return os << "PrimaryGChromaticityX"; + case Id::kPrimaryGChromaticityY: + return os << "PrimaryGChromaticityY"; + case Id::kPrimaryBChromaticityX: + return os << "PrimaryBChromaticityX"; + case Id::kPrimaryBChromaticityY: + return os << "PrimaryBChromaticityY"; + case Id::kWhitePointChromaticityX: + return os << "WhitePointChromaticityX"; + case Id::kWhitePointChromaticityY: + return os << "WhitePointChromaticityY"; + case Id::kLuminanceMax: + return os << "LuminanceMax"; + case Id::kLuminanceMin: + return os << "LuminanceMin"; + case Id::kProjection: + return os << "Projection"; + case Id::kProjectionType: + return os << "kProjectionType"; + case Id::kProjectionPrivate: + return os << "kProjectionPrivate"; + case Id::kProjectionPoseYaw: + return os << "kProjectionPoseYaw"; + case Id::kProjectionPosePitch: + return os << "kProjectionPosePitch"; + case Id::kProjectionPoseRoll: + return os << "ProjectionPoseRoll"; + case Id::kAudio: + return os << "Audio"; + case Id::kSamplingFrequency: + return os << "SamplingFrequency"; + case Id::kOutputSamplingFrequency: + return os << "OutputSamplingFrequency"; + case Id::kChannels: + return os << "Channels"; + case Id::kBitDepth: + return os << "BitDepth"; + case Id::kContentEncodings: + return os << "ContentEncodings"; + case Id::kContentEncoding: + return os << "ContentEncoding"; + case Id::kContentEncodingOrder: + return os << "ContentEncodingOrder"; + case Id::kContentEncodingScope: + return os << "ContentEncodingScope"; + case Id::kContentEncodingType: + return os << "ContentEncodingType"; + case Id::kContentEncryption: + return os << "ContentEncryption"; + case Id::kContentEncAlgo: + return os << "ContentEncAlgo"; + case Id::kContentEncKeyId: + return os << "ContentEncKeyID"; + case Id::kContentEncAesSettings: + return os << "ContentEncAESSettings"; + case Id::kAesSettingsCipherMode: + return os << "AESSettingsCipherMode"; + case Id::kCues: + return os << "Cues"; + case Id::kCuePoint: + return os << "CuePoint"; + case Id::kCueTime: + return os << "CueTime"; + case Id::kCueTrackPositions: + return os << "CueTrackPositions"; + case Id::kCueTrack: + return os << "CueTrack"; + case Id::kCueClusterPosition: + return os << "CueClusterPosition"; + case Id::kCueRelativePosition: + return os << "CueRelativePosition"; + case Id::kCueDuration: + return os << "CueDuration"; + case Id::kCueBlockNumber: + return os << "CueBlockNumber"; + case Id::kChapters: + return os << "Chapters"; + case Id::kEditionEntry: + return os << "EditionEntry"; + case Id::kChapterAtom: + return os << "ChapterAtom"; + case Id::kChapterUid: + return os << "ChapterUID"; + case Id::kChapterStringUid: + return os << "ChapterStringUID"; + case Id::kChapterTimeStart: + return os << "ChapterTimeStart"; + case Id::kChapterTimeEnd: + return os << "ChapterTimeEnd"; + case Id::kChapterDisplay: + return os << "ChapterDisplay"; + case Id::kChapString: + return os << "ChapString"; + case Id::kChapLanguage: + return os << "ChapLanguage"; + case Id::kChapCountry: + return os << "ChapCountry"; + case Id::kTags: + return os << "Tags"; + case Id::kTag: + return os << "Tag"; + case Id::kTargets: + return os << "Targets"; + case Id::kTargetTypeValue: + return os << "TargetTypeValue"; + case Id::kTargetType: + return os << "TargetType"; + case Id::kTagTrackUid: + return os << "TagTrackUID"; + case Id::kSimpleTag: + return os << "SimpleTag"; + case Id::kTagName: + return os << "TagName"; + case Id::kTagLanguage: + return os << "TagLanguage"; + case Id::kTagDefault: + return os << "TagDefault"; + case Id::kTagString: + return os << "TagString"; + case Id::kTagBinary: + return os << "TagBinary"; + default: + return PrintUnknownEnumValue(os, id); + } +} + +std::ostream& operator<<(std::ostream& os, Lacing value) { + switch (value) { + case Lacing::kNone: + return os << "0 (none)"; + case Lacing::kXiph: + return os << "2 (Xiph)"; + case Lacing::kFixed: + return os << "4 (fixed)"; + case Lacing::kEbml: + return os << "6 (EBML)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, MatrixCoefficients value) { + switch (value) { + case MatrixCoefficients::kRgb: + return os << "0 (identity, RGB/XYZ)"; + case MatrixCoefficients::kBt709: + return os << "1 (Rec. ITU-R BT.709-5)"; + case MatrixCoefficients::kUnspecified: + return os << "2 (unspecified)"; + case MatrixCoefficients::kFcc: + return os << "4 (US FCC)"; + case MatrixCoefficients::kBt470Bg: + return os << "5 (Rec. ITU-R BT.470-6 System B, G)"; + case MatrixCoefficients::kSmpte170M: + return os << "6 (SMPTE 170M)"; + case MatrixCoefficients::kSmpte240M: + return os << "7 (SMPTE 240M)"; + case MatrixCoefficients::kYCgCo: + return os << "8 (YCgCo)"; + case MatrixCoefficients::kBt2020NonconstantLuminance: + return os << "9 (Rec. ITU-R BT.2020, non-constant luma)"; + case MatrixCoefficients::kBt2020ConstantLuminance: + return os << "10 (Rec. ITU-R BT.2020 , constant luma)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, Range value) { + switch (value) { + case Range::kUnspecified: + return os << "0 (unspecified)"; + case Range::kBroadcast: + return os << "1 (broadcast)"; + case Range::kFull: + return os << "2 (full)"; + case Range::kDerived: + return os << "3 (defined by MatrixCoefficients/TransferCharacteristics)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, TransferCharacteristics value) { + switch (value) { + case TransferCharacteristics::kBt709: + return os << "1 (Rec. ITU-R BT.709-6)"; + case TransferCharacteristics::kUnspecified: + return os << "2 (unspecified)"; + case TransferCharacteristics::kGamma22curve: + return os << "4 (gamma 2.2, Rec. ITU‑R BT.470‑6 System M)"; + case TransferCharacteristics::kGamma28curve: + return os << "5 (gamma 2.8, Rec. ITU‑R BT.470-6 System B, G)"; + case TransferCharacteristics::kSmpte170M: + return os << "6 (SMPTE 170M)"; + case TransferCharacteristics::kSmpte240M: + return os << "7 (SMPTE 240M)"; + case TransferCharacteristics::kLinear: + return os << "8 (linear)"; + case TransferCharacteristics::kLog: + return os << "9 (log, 100:1 range)"; + case TransferCharacteristics::kLogSqrt: + return os << "10 (log, 316.2:1 range)"; + case TransferCharacteristics::kIec6196624: + return os << "11 (IEC 61966-2-4)"; + case TransferCharacteristics::kBt1361ExtendedColourGamut: + return os << "12 (Rec. ITU-R BT.1361, extended colour gamut)"; + case TransferCharacteristics::kIec6196621: + return os << "13 (IEC 61966-2-1, sRGB or sYCC)"; + case TransferCharacteristics::k10BitBt2020: + return os << "14 (Rec. ITU-R BT.2020-2, 10-bit)"; + case TransferCharacteristics::k12BitBt2020: + return os << "15 (Rec. ITU-R BT.2020-2, 12-bit)"; + case TransferCharacteristics::kSmpteSt2084: + return os << "16 (SMPTE ST 2084)"; + case TransferCharacteristics::kSmpteSt4281: + return os << "17 (SMPTE ST 428-1)"; + case TransferCharacteristics::kAribStdB67Hlg: + return os << "18 (ARIB STD-B67/Rec. ITU-R BT.[HDR-TV] HLG)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, Primaries value) { + switch (value) { + case Primaries::kBt709: + return os << "1 (Rec. ITU‑R BT.709-6)"; + case Primaries::kUnspecified: + return os << "2 (unspecified)"; + case Primaries::kBt470M: + return os << "4 (Rec. ITU‑R BT.470‑6 System M)"; + case Primaries::kBt470Bg: + return os << "5 (Rec. ITU‑R BT.470‑6 System B, G)"; + case Primaries::kSmpte170M: + return os << "6 (SMPTE 170M)"; + case Primaries::kSmpte240M: + return os << "7 (SMPTE 240M)"; + case Primaries::kFilm: + return os << "8 (generic film)"; + case Primaries::kBt2020: + return os << "9 (Rec. ITU-R BT.2020-2)"; + case Primaries::kSmpteSt4281: + return os << "10 (SMPTE ST 428-1)"; + case Primaries::kJedecP22Phosphors: + return os << "22 (EBU Tech. 3213-E/JEDEC P22 phosphors)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, ProjectionType value) { + switch (value) { + case ProjectionType::kRectangular: + return os << "0 (rectangular)"; + case ProjectionType::kEquirectangular: + return os << "1 (equirectangular)"; + case ProjectionType::kCubeMap: + return os << "2 (cube map)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, FlagInterlaced value) { + switch (value) { + case FlagInterlaced::kUnspecified: + return os << "0 (unspecified)"; + case FlagInterlaced::kInterlaced: + return os << "1 (interlaced)"; + case FlagInterlaced::kProgressive: + return os << "2 (progressive)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, StereoMode value) { + switch (value) { + case StereoMode::kMono: + return os << "0 (mono)"; + case StereoMode::kSideBySideLeftFirst: + return os << "1 (side-by-side, left eye first)"; + case StereoMode::kTopBottomRightFirst: + return os << "2 (top-bottom, right eye first)"; + case StereoMode::kTopBottomLeftFirst: + return os << "3 (top-bottom, left eye first)"; + case StereoMode::kCheckboardRightFirst: + return os << "4 (checkboard, right eye first)"; + case StereoMode::kCheckboardLeftFirst: + return os << "5 (checkboard, left eye first)"; + case StereoMode::kRowInterleavedRightFirst: + return os << "6 (row interleaved, right eye first)"; + case StereoMode::kRowInterleavedLeftFirst: + return os << "7 (row interleaved, left eye first)"; + case StereoMode::kColumnInterleavedRightFirst: + return os << "8 (column interleaved, right eye first)"; + case StereoMode::kColumnInterleavedLeftFirst: + return os << "9 (column interleaved, left eye first)"; + case StereoMode::kAnaglyphCyanRed: + return os << "10 (anaglyph, cyan/red)"; + case StereoMode::kSideBySideRightFirst: + return os << "11 (side-by-side, right eye first)"; + case StereoMode::kAnaglyphGreenMagenta: + return os << "12 (anaglyph, green/magenta)"; + case StereoMode::kBlockLacedLeftFirst: + return os << "13 (block laced, left eye first)"; + case StereoMode::kBlockLacedRightFirst: + return os << "14 (block laced, right eye first)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, DisplayUnit value) { + switch (value) { + case DisplayUnit::kPixels: + return os << "0 (pixels)"; + case DisplayUnit::kCentimeters: + return os << "1 (centimeters)"; + case DisplayUnit::kInches: + return os << "2 (inches)"; + case DisplayUnit::kDisplayAspectRatio: + return os << "3 (display aspect ratio)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, AspectRatioType value) { + switch (value) { + case AspectRatioType::kFreeResizing: + return os << "0 (free resizing)"; + case AspectRatioType::kKeep: + return os << "1 (keep aspect ratio)"; + case AspectRatioType::kFixed: + return os << "2 (fixed)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, AesSettingsCipherMode value) { + switch (value) { + case AesSettingsCipherMode::kCtr: + return os << "1 (CTR)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, ContentEncAlgo value) { + switch (value) { + case ContentEncAlgo::kOnlySigned: + return os << "0 (only signed, not encrypted)"; + case ContentEncAlgo::kDes: + return os << "1 (DES)"; + case ContentEncAlgo::k3Des: + return os << "2 (3DES)"; + case ContentEncAlgo::kTwofish: + return os << "3 (Twofish)"; + case ContentEncAlgo::kBlowfish: + return os << "4 (Blowfish)"; + case ContentEncAlgo::kAes: + return os << "5 (AES)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, ContentEncodingType value) { + switch (value) { + case ContentEncodingType::kCompression: + return os << "0 (compression)"; + case ContentEncodingType::kEncryption: + return os << "1 (encryption)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +std::ostream& operator<<(std::ostream& os, TrackType value) { + switch (value) { + case TrackType::kVideo: + return os << "1 (video)"; + case TrackType::kAudio: + return os << "2 (audio)"; + case TrackType::kComplex: + return os << "3 (complex)"; + case TrackType::kLogo: + return os << "16 (logo)"; + case TrackType::kSubtitle: + return os << "17 (subtitle)"; + case TrackType::kButtons: + return os << "18 (buttons)"; + case TrackType::kControl: + return os << "32 (control)"; + default: + return PrintUnknownEnumValue(os, value); + } +} + +// For binary elements, just print out its size. +std::ostream& operator<<(std::ostream& os, + const std::vector<std::uint8_t>& value) { + return os << '<' << value.size() << " bytes>"; +} + +class DemoCallback : public Callback { + public: + int indent = 0; + int spaces_per_indent = 2; + + void PrintElementMetadata(const std::string& name, + const ElementMetadata& metadata) { + // Since we aren't doing any seeking in this demo, we don't have to worry + // about kUnknownHeaderSize or kUnknownElementPosition when adding the + // position and sizes. + const std::uint64_t header_start = metadata.position; + const std::uint64_t header_end = header_start + metadata.header_size; + const std::uint64_t body_start = header_end; + std::cout << std::string(indent * spaces_per_indent, ' ') << name; + // The ContentEncAESSettings element has the longest name (out of all other + // master elements) at 21 characters. It's also the deepest master element + // at a level of 6. Insert enough whitespace so there's room for it. + std::cout << std::string(21 + 6 * spaces_per_indent - + indent * spaces_per_indent - name.size(), + ' ') + << " header: [" << header_start << ", " << header_end + << ") body: [" << body_start << ", "; + if (metadata.size != kUnknownElementSize) { + const std::uint64_t body_end = body_start + metadata.size; + std::cout << body_end; + } else { + std::cout << '?'; + } + std::cout << ")\n"; + } + + template <typename T> + void PrintMandatoryElement(const std::string& name, + const Element<T>& element) { + std::cout << std::string(indent * spaces_per_indent, ' ') << name; + if (!element.is_present()) { + std::cout << " (implicit)"; + } + std::cout << ": " << element.value() << '\n'; + } + + template <typename T> + void PrintMandatoryElement(const std::string& name, + const std::vector<Element<T>>& elements) { + for (const Element<T>& element : elements) { + PrintMandatoryElement(name, element); + } + } + + template <typename T> + void PrintOptionalElement(const std::string& name, + const Element<T>& element) { + if (element.is_present()) { + std::cout << std::string(indent * spaces_per_indent, ' ') << name << ": " + << element.value() << '\n'; + } + } + + template <typename T> + void PrintOptionalElement(const std::string& name, + const std::vector<Element<T>>& elements) { + for (const Element<T>& element : elements) { + PrintOptionalElement(name, element); + } + } + + void PrintMasterElement(const BlockAdditions& block_additions) { + PrintMasterElement("BlockMore", block_additions.block_mores); + } + + void PrintMasterElement(const BlockMore& block_more) { + PrintMandatoryElement("BlockAddID", block_more.id); + PrintMandatoryElement("BlockAdditional", block_more.data); + } + + void PrintMasterElement(const Slices& slices) { + PrintMasterElement("TimeSlice", slices.slices); + } + + void PrintMasterElement(const TimeSlice& time_slice) { + PrintOptionalElement("LaceNumber", time_slice.lace_number); + } + + void PrintMasterElement(const Video& video) { + PrintMandatoryElement("FlagInterlaced", video.interlaced); + PrintOptionalElement("StereoMode", video.stereo_mode); + PrintOptionalElement("AlphaMode", video.alpha_mode); + PrintMandatoryElement("PixelWidth", video.pixel_width); + PrintMandatoryElement("PixelHeight", video.pixel_height); + PrintOptionalElement("PixelCropBottom", video.pixel_crop_bottom); + PrintOptionalElement("PixelCropTop", video.pixel_crop_top); + PrintOptionalElement("PixelCropLeft", video.pixel_crop_left); + PrintOptionalElement("PixelCropRight", video.pixel_crop_right); + PrintOptionalElement("DisplayWidth", video.display_width); + PrintOptionalElement("DisplayHeight", video.display_height); + PrintOptionalElement("DisplayUnit", video.display_unit); + PrintOptionalElement("AspectRatioType", video.aspect_ratio_type); + PrintOptionalElement("FrameRate", video.frame_rate); + PrintMasterElement("Colour", video.colour); + PrintMasterElement("Projection", video.projection); + } + + void PrintMasterElement(const Colour& colour) { + PrintOptionalElement("MatrixCoefficients", colour.matrix_coefficients); + PrintOptionalElement("BitsPerChannel", colour.bits_per_channel); + PrintOptionalElement("ChromaSubsamplingHorz", colour.chroma_subsampling_x); + PrintOptionalElement("ChromaSubsamplingVert", colour.chroma_subsampling_y); + PrintOptionalElement("CbSubsamplingHorz", colour.cb_subsampling_x); + PrintOptionalElement("CbSubsamplingVert", colour.cb_subsampling_y); + PrintOptionalElement("ChromaSitingHorz", colour.chroma_siting_x); + PrintOptionalElement("ChromaSitingVert", colour.chroma_siting_y); + PrintOptionalElement("Range", colour.range); + PrintOptionalElement("TransferCharacteristics", + colour.transfer_characteristics); + PrintOptionalElement("Primaries", colour.primaries); + PrintOptionalElement("MaxCLL", colour.max_cll); + PrintOptionalElement("MaxFALL", colour.max_fall); + PrintMasterElement("MasteringMetadata", colour.mastering_metadata); + } + + void PrintMasterElement(const MasteringMetadata& mastering_metadata) { + PrintOptionalElement("PrimaryRChromaticityX", + mastering_metadata.primary_r_chromaticity_x); + PrintOptionalElement("PrimaryRChromaticityY", + mastering_metadata.primary_r_chromaticity_y); + PrintOptionalElement("PrimaryGChromaticityX", + mastering_metadata.primary_g_chromaticity_x); + PrintOptionalElement("PrimaryGChromaticityY", + mastering_metadata.primary_g_chromaticity_y); + PrintOptionalElement("PrimaryBChromaticityX", + mastering_metadata.primary_b_chromaticity_x); + PrintOptionalElement("PrimaryBChromaticityY", + mastering_metadata.primary_b_chromaticity_y); + PrintOptionalElement("WhitePointChromaticityX", + mastering_metadata.white_point_chromaticity_x); + PrintOptionalElement("WhitePointChromaticityY", + mastering_metadata.white_point_chromaticity_y); + PrintOptionalElement("LuminanceMax", mastering_metadata.luminance_max); + PrintOptionalElement("LuminanceMin", mastering_metadata.luminance_min); + } + + void PrintMasterElement(const Projection& projection) { + PrintMandatoryElement("ProjectionType", projection.type); + PrintOptionalElement("ProjectionPrivate", projection.projection_private); + PrintMandatoryElement("ProjectionPoseYaw", projection.pose_yaw); + PrintMandatoryElement("ProjectionPosePitch", projection.pose_pitch); + PrintMandatoryElement("ProjectionPoseRoll", projection.pose_roll); + } + + void PrintMasterElement(const Audio& audio) { + PrintMandatoryElement("SamplingFrequency", audio.sampling_frequency); + PrintOptionalElement("OutputSamplingFrequency", audio.output_frequency); + PrintMandatoryElement("Channels", audio.channels); + PrintOptionalElement("BitDepth", audio.bit_depth); + } + + void PrintMasterElement(const ContentEncodings& content_encodings) { + PrintMasterElement("ContentEncoding", content_encodings.encodings); + } + + void PrintMasterElement(const ContentEncoding& content_encoding) { + PrintMandatoryElement("ContentEncodingOrder", content_encoding.order); + PrintMandatoryElement("ContentEncodingScope", content_encoding.scope); + PrintMandatoryElement("ContentEncodingType", content_encoding.type); + PrintMasterElement("ContentEncryption", content_encoding.encryption); + } + + void PrintMasterElement(const ContentEncryption& content_encryption) { + PrintOptionalElement("ContentEncAlgo", content_encryption.algorithm); + PrintOptionalElement("ContentEncKeyID", content_encryption.key_id); + PrintMasterElement("ContentEncAESSettings", + content_encryption.aes_settings); + } + + void PrintMasterElement( + const ContentEncAesSettings& content_enc_aes_settings) { + PrintMandatoryElement("AESSettingsCipherMode", + content_enc_aes_settings.aes_settings_cipher_mode); + } + + void PrintMasterElement(const CueTrackPositions& cue_track_positions) { + PrintMandatoryElement("CueTrack", cue_track_positions.track); + PrintMandatoryElement("CueClusterPosition", + cue_track_positions.cluster_position); + PrintOptionalElement("CueRelativePosition", + cue_track_positions.relative_position); + PrintOptionalElement("CueDuration", cue_track_positions.duration); + PrintOptionalElement("CueBlockNumber", cue_track_positions.block_number); + } + + void PrintMasterElement(const ChapterAtom& chapter_atom) { + PrintMandatoryElement("ChapterUID", chapter_atom.uid); + PrintOptionalElement("ChapterStringUID", chapter_atom.string_uid); + PrintMandatoryElement("ChapterTimeStart", chapter_atom.time_start); + PrintOptionalElement("ChapterTimeEnd", chapter_atom.time_end); + PrintMasterElement("ChapterDisplay", chapter_atom.displays); + PrintMasterElement("ChapterAtom", chapter_atom.atoms); + } + + void PrintMasterElement(const ChapterDisplay& chapter_display) { + PrintMandatoryElement("ChapString", chapter_display.string); + PrintMandatoryElement("ChapLanguage", chapter_display.languages); + PrintOptionalElement("ChapCountry", chapter_display.countries); + } + + void PrintMasterElement(const Targets& targets) { + PrintOptionalElement("TargetTypeValue", targets.type_value); + PrintOptionalElement("TargetType", targets.type); + PrintMandatoryElement("TagTrackUID", targets.track_uids); + } + + void PrintMasterElement(const SimpleTag& simple_tag) { + PrintMandatoryElement("TagName", simple_tag.name); + PrintMandatoryElement("TagLanguage", simple_tag.language); + PrintMandatoryElement("TagDefault", simple_tag.is_default); + PrintOptionalElement("TagString", simple_tag.string); + PrintOptionalElement("TagBinary", simple_tag.binary); + PrintMasterElement("SimpleTag", simple_tag.tags); + } + + // When printing a master element that's wrapped in Element<>, peel off the + // Element<> wrapper and print the underlying master element if it's present. + template <typename T> + void PrintMasterElement(const std::string& name, const Element<T>& element) { + if (element.is_present()) { + std::cout << std::string(indent * spaces_per_indent, ' ') << name << "\n"; + ++indent; + PrintMasterElement(element.value()); + --indent; + } + } + + template <typename T> + void PrintMasterElement(const std::string& name, + const std::vector<Element<T>>& elements) { + for (const Element<T>& element : elements) { + PrintMasterElement(name, element); + } + } + + template <typename T> + void PrintValue(const std::string& name, const T& value) { + std::cout << std::string(indent * spaces_per_indent, ' ') << name << ": " + << value << '\n'; + } + + Status OnElementBegin(const ElementMetadata& metadata, + Action* action) override { + // Print out metadata for some level 1 elements that don't have explicit + // callbacks. + switch (metadata.id) { + case Id::kSeekHead: + indent = 1; + PrintElementMetadata("SeekHead", metadata); + break; + case Id::kTracks: + indent = 1; + PrintElementMetadata("Tracks", metadata); + break; + case Id::kCues: + indent = 1; + PrintElementMetadata("Cues", metadata); + break; + case Id::kChapters: + indent = 1; + PrintElementMetadata("Chapters", metadata); + break; + case Id::kTags: + indent = 1; + PrintElementMetadata("Tags", metadata); + break; + default: + break; + } + + *action = Action::kRead; + return Status(Status::kOkCompleted); + } + + Status OnUnknownElement(const ElementMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining) override { + // Output unknown elements without any indentation because we aren't + // tracking which element contains them. + int original_indent = indent; + indent = 0; + PrintElementMetadata("UNKNOWN_ELEMENT!", metadata); + indent = original_indent; + // The base class's implementation will just skip the element via + // Reader::Skip(). + return Callback::OnUnknownElement(metadata, reader, bytes_remaining); + } + + Status OnEbml(const ElementMetadata& metadata, const Ebml& ebml) override { + indent = 0; + PrintElementMetadata("EBML", metadata); + indent = 1; + PrintMandatoryElement("EBMLVersion", ebml.ebml_version); + PrintMandatoryElement("EBMLReadVersion", ebml.ebml_read_version); + PrintMandatoryElement("EBMLMaxIDLength", ebml.ebml_max_id_length); + PrintMandatoryElement("EBMLMaxSizeLength", ebml.ebml_max_size_length); + PrintMandatoryElement("DocType", ebml.doc_type); + PrintMandatoryElement("DocTypeVersion", ebml.doc_type_version); + PrintMandatoryElement("DocTypeReadVersion", ebml.doc_type_read_version); + return Status(Status::kOkCompleted); + } + + Status OnVoid(const ElementMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining) override { + // Output Void elements without any indentation because we aren't tracking + // which element contains them. + int original_indent = indent; + indent = 0; + PrintElementMetadata("Void", metadata); + indent = original_indent; + // The base class's implementation will just skip the element via + // Reader::Skip(). + return Callback::OnVoid(metadata, reader, bytes_remaining); + } + + Status OnSegmentBegin(const ElementMetadata& metadata, + Action* action) override { + indent = 0; + PrintElementMetadata("Segment", metadata); + indent = 1; + *action = Action::kRead; + return Status(Status::kOkCompleted); + } + + Status OnSeek(const ElementMetadata& metadata, const Seek& seek) override { + indent = 2; + PrintElementMetadata("Seek", metadata); + indent = 3; + PrintMandatoryElement("SeekID", seek.id); + PrintMandatoryElement("SeekPosition", seek.position); + return Status(Status::kOkCompleted); + } + + Status OnInfo(const ElementMetadata& metadata, const Info& info) override { + indent = 1; + PrintElementMetadata("Info", metadata); + indent = 2; + PrintMandatoryElement("TimecodeScale", info.timecode_scale); + PrintOptionalElement("Duration", info.duration); + PrintOptionalElement("DateUTC", info.date_utc); + PrintOptionalElement("Title", info.title); + PrintOptionalElement("MuxingApp", info.muxing_app); + PrintOptionalElement("WritingApp", info.writing_app); + return Status(Status::kOkCompleted); + } + + Status OnClusterBegin(const ElementMetadata& metadata, const Cluster& cluster, + Action* action) override { + indent = 1; + PrintElementMetadata("Cluster", metadata); + // A properly muxed file will have Timecode and PrevSize first before any + // SimpleBlock or BlockGroups. The parser takes advantage of this and delays + // calling OnClusterBegin() until it hits the first SimpleBlock or + // BlockGroup child (or the Cluster ends if it's empty). It's possible for + // an improperly muxed file to have Timecode or PrevSize after the first + // block, in which case they'll be absent here and may be accessed in + // OnClusterEnd() when the Cluster and all its children have been fully + // parsed. In this demo we assume the file has been properly muxed. + indent = 2; + PrintMandatoryElement("Timecode", cluster.timecode); + PrintOptionalElement("PrevSize", cluster.previous_size); + *action = Action::kRead; + return Status(Status::kOkCompleted); + } + + Status OnSimpleBlockBegin(const ElementMetadata& metadata, + const SimpleBlock& simple_block, + Action* action) override { + indent = 2; + PrintElementMetadata("SimpleBlock", metadata); + indent = 3; + PrintValue("track number", simple_block.track_number); + PrintValue("frames", simple_block.num_frames); + PrintValue("timecode", simple_block.timecode); + PrintValue("lacing", simple_block.lacing); + std::string flags = (simple_block.is_visible) ? "visible" : "invisible"; + if (simple_block.is_key_frame) + flags += ", key frame"; + if (simple_block.is_discardable) + flags += ", discardable"; + PrintValue("flags", flags); + *action = Action::kRead; + return Status(Status::kOkCompleted); + } + + Status OnSimpleBlockEnd(const ElementMetadata& /* metadata */, + const SimpleBlock& /* simple_block */) override { + return Status(Status::kOkCompleted); + } + + Status OnBlockGroupBegin(const ElementMetadata& metadata, + Action* action) override { + indent = 2; + PrintElementMetadata("BlockGroup", metadata); + *action = Action::kRead; + return Status(Status::kOkCompleted); + } + + Status OnBlockBegin(const ElementMetadata& metadata, const Block& block, + Action* action) override { + indent = 3; + PrintElementMetadata("Block", metadata); + indent = 4; + PrintValue("track number", block.track_number); + PrintValue("frames", block.num_frames); + PrintValue("timecode", block.timecode); + PrintValue("lacing", block.lacing); + PrintValue("flags", (block.is_visible) ? "visible" : "invisible"); + *action = Action::kRead; + return Status(Status::kOkCompleted); + } + + Status OnBlockEnd(const ElementMetadata& /* metadata */, + const Block& /* block */) override { + return Status(Status::kOkCompleted); + } + + Status OnBlockGroupEnd(const ElementMetadata& /* metadata */, + const BlockGroup& block_group) override { + if (block_group.virtual_block.is_present()) { + std::cout << std::string(indent * spaces_per_indent, ' ') + << "BlockVirtual\n"; + indent = 4; + PrintValue("track number", + block_group.virtual_block.value().track_number); + PrintValue("timecode", block_group.virtual_block.value().timecode); + } + indent = 3; + PrintMasterElement("BlockAdditions", block_group.additions); + PrintOptionalElement("BlockDuration", block_group.duration); + PrintOptionalElement("ReferenceBlock", block_group.references); + PrintOptionalElement("DiscardPadding", block_group.discard_padding); + PrintMasterElement("Slices", block_group.slices); + // BlockGroup::block has been set, but we've already printed it in + // OnBlockBegin(). + return Status(Status::kOkCompleted); + } + + Status OnFrame(const FrameMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining) override { + PrintValue("frame byte range", + '[' + std::to_string(metadata.position) + ", " + + std::to_string(metadata.position + metadata.size) + ')'); + // The base class's implementation will just skip the frame via + // Reader::Skip(). + return Callback::OnFrame(metadata, reader, bytes_remaining); + } + + Status OnClusterEnd(const ElementMetadata& /* metadata */, + const Cluster& /* cluster */) override { + // The Cluster and all its children have been fully parsed at this point. If + // the file wasn't properly muxed and Timecode or PrevSize were missing in + // OnClusterBegin(), they'll be set here (if the Cluster contained them). In + // this demo we already handled them, though. + return Status(Status::kOkCompleted); + } + + Status OnTrackEntry(const ElementMetadata& metadata, + const TrackEntry& track_entry) override { + indent = 2; + PrintElementMetadata("TrackEntry", metadata); + indent = 3; + PrintMandatoryElement("TrackNumber", track_entry.track_number); + PrintMandatoryElement("TrackUID", track_entry.track_uid); + PrintMandatoryElement("TrackType", track_entry.track_type); + PrintMandatoryElement("FlagEnabled", track_entry.is_enabled); + PrintMandatoryElement("FlagDefault", track_entry.is_default); + PrintMandatoryElement("FlagForced", track_entry.is_forced); + PrintMandatoryElement("FlagLacing", track_entry.uses_lacing); + PrintOptionalElement("DefaultDuration", track_entry.default_duration); + PrintOptionalElement("Name", track_entry.name); + PrintOptionalElement("Language", track_entry.language); + PrintMandatoryElement("CodecID", track_entry.codec_id); + PrintOptionalElement("CodecPrivate", track_entry.codec_private); + PrintOptionalElement("CodecName", track_entry.codec_name); + PrintOptionalElement("CodecDelay", track_entry.codec_delay); + PrintMandatoryElement("SeekPreRoll", track_entry.seek_pre_roll); + PrintMasterElement("Video", track_entry.video); + PrintMasterElement("Audio", track_entry.audio); + PrintMasterElement("ContentEncodings", track_entry.content_encodings); + return Status(Status::kOkCompleted); + } + + Status OnCuePoint(const ElementMetadata& metadata, + const CuePoint& cue_point) override { + indent = 2; + PrintElementMetadata("CuePoint", metadata); + indent = 3; + PrintMandatoryElement("CueTime", cue_point.time); + PrintMasterElement("CueTrackPositions", cue_point.cue_track_positions); + return Status(Status::kOkCompleted); + } + + Status OnEditionEntry(const ElementMetadata& metadata, + const EditionEntry& edition_entry) override { + indent = 2; + PrintElementMetadata("EditionEntry", metadata); + indent = 3; + PrintMasterElement("ChapterAtom", edition_entry.atoms); + return Status(Status::kOkCompleted); + } + + Status OnTag(const ElementMetadata& metadata, const Tag& tag) override { + indent = 2; + PrintElementMetadata("Tag", metadata); + indent = 3; + PrintMasterElement("Targets", tag.targets); + PrintMasterElement("SimpleTag", tag.tags); + return Status(Status::kOkCompleted); + } + + Status OnSegmentEnd(const ElementMetadata& /* metadata */) override { + return Status(Status::kOkCompleted); + } +}; + +int main(int argc, char* argv[]) { + if ((argc != 1 && argc != 2) || + (argc == 2 && argv[1] == std::string("--help"))) { + std::cerr << "Usage:\n" + << argv[0] << " [path-to-webm-file]\n\n" + << "Prints info for the WebM file specified in the command line. " + "If no file is\n" + << "specified, stdin is used as input.\n"; + return EXIT_FAILURE; + } + + FILE* file = (argc == 2) ? std::fopen(argv[1], "rb") + : std::freopen(nullptr, "rb", stdin); + if (!file) { + std::cerr << "File cannot be opened\n"; + return EXIT_FAILURE; + } + + FileReader reader(file); + DemoCallback callback; + WebmParser parser; + Status status = parser.Feed(&callback, &reader); + if (!status.completed_ok()) { + std::cerr << "Parsing error; status code: " << status.code << '\n'; + return EXIT_FAILURE; + } + + return 0; +} diff --git a/webm_parser/doxygen.config b/webm_parser/doxygen.config new file mode 100644 index 0000000..f2aae8a --- /dev/null +++ b/webm_parser/doxygen.config @@ -0,0 +1,319 @@ +# Doxyfile 1.8.11 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "WebM Parser" +PROJECT_NUMBER = +PROJECT_BRIEF = +PROJECT_LOGO = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 2 +ALIASES = MatroskaID{1}="[\1](https://www.webmproject.org/docs/container/#\1) ([Matroska definition](https://matroska.org/technical/specs/index.html#\1))" \ + WebMID{1}="[\1](https://www.webmproject.org/docs/container/#\1)" \ + WebMTable{7}="| Type | Level | Mandatory | Multiple | Recursive | Value range | Default value |\n| ---- | ----- | --------- | -------- | --------- | ----------- | ------------- |\n| \1 | \2 | \3 | \4 | \5 | \6 | \7 |\n" +TCL_SUBST = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +EXTENSION_MAPPING = +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +FILE_VERSION_FILTER = +LAYOUT_FILE = +CITE_BIB_FILES = +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = README.md include/webm +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +FILTER_SOURCE_PATTERNS = +USE_MDFILE_AS_MAINPAGE = README.md +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_EXTRA_STYLESHEET = +HTML_EXTRA_FILES = +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +CHM_INDEX_ENCODING = +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QCH_FILE = +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = +QHG_LOCATION = +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +FORMULA_FONTSIZE = 10 +FORMULA_TRANSPARENT = YES +USE_MATHJAX = NO +MATHJAX_FORMAT = HTML-CSS +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +MATHJAX_EXTENSIONS = +MATHJAX_CODEFILE = +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHENGINE_URL = +SEARCHDATA_FILE = searchdata.xml +EXTERNAL_SEARCH_ID = +EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +EXTRA_PACKAGES = +LATEX_HEADER = +LATEX_FOOTER = +LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_FILES = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_SOURCE_CODE = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +RTF_SOURCE_CODE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_SUBDIR = +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +DOCBOOK_PROGRAMLISTING = NO +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +DIA_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_FONTNAME = Helvetica +DOT_FONTSIZE = 10 +DOT_FONTPATH = +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_PATH = +DOTFILE_DIRS = +MSCFILE_DIRS = +DIAFILE_DIRS = +PLANTUML_JAR_PATH = +PLANTUML_INCLUDE_PATH = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/webm_parser/fuzzing/corpus/00805c2543756a5fd85652d03bfbbd2eb6192ca5 b/webm_parser/fuzzing/corpus/00805c2543756a5fd85652d03bfbbd2eb6192ca5 new file mode 100644 index 0000000..d78507e --- /dev/null +++ b/webm_parser/fuzzing/corpus/00805c2543756a5fd85652d03bfbbd2eb6192ca5 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P1S€g”T®k®m€Šb@‡P5„Gáÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/00d120eb143bb02c48d7c863e5826d2ad1a6da4b b/webm_parser/fuzzing/corpus/00d120eb143bb02c48d7c863e5826d2ad1a6da4b new file mode 100644 index 0000000..2d4403c --- /dev/null +++ b/webm_parser/fuzzing/corpus/00d120eb143bb02c48d7c863e5826d2ad1a6da4b @@ -0,0 +1 @@ +S€g‹T®k†®„c¢!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/018dee8285e9e20ca3996bb2dc0284b5c57ba75a b/webm_parser/fuzzing/corpus/018dee8285e9e20ca3996bb2dc0284b5c57ba75a new file mode 100644 index 0000000..2920522 --- /dev/null +++ b/webm_parser/fuzzing/corpus/018dee8285e9e20ca3996bb2dc0284b5c57ba75a @@ -0,0 +1 @@ +S€g…C¶u€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/020edb59637c1e6439f19aa3a5a9d50c3377dbe9 b/webm_parser/fuzzing/corpus/020edb59637c1e6439f19aa3a5a9d50c3377dbe9 new file mode 100644 index 0000000..c194606 --- /dev/null +++ b/webm_parser/fuzzing/corpus/020edb59637c1e6439f19aa3a5a9d50c3377dbe9 @@ -0,0 +1 @@ +S€g“T®kŽ®Œm€‰b@†P3ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/0247ce2b1a71752a3af11e1065ca90afa0df9d30 b/webm_parser/fuzzing/corpus/0247ce2b1a71752a3af11e1065ca90afa0df9d30 new file mode 100644 index 0000000..42b8525 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0247ce2b1a71752a3af11e1065ca90afa0df9d30 @@ -0,0 +1 @@ +S€gC¶u‹ ‰Ž‡è…̃
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/028b19f7d79f5da7a2af13a0c1e2d13f7eaf24cd b/webm_parser/fuzzing/corpus/028b19f7d79f5da7a2af13a0c1e2d13f7eaf24cd new file mode 100644 index 0000000..863b42b --- /dev/null +++ b/webm_parser/fuzzing/corpus/028b19f7d79f5da7a2af13a0c1e2d13f7eaf24cd @@ -0,0 +1 @@ +S€gTÿk†à®ˆTº„ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/029ab55b16df41881f8de2351205201da334550a b/webm_parser/fuzzing/corpus/029ab55b16df41881f8de2351205201da334550a new file mode 100644 index 0000000..27523d4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/029ab55b16df41881f8de2351205201da334550a @@ -0,0 +1 @@ +S€gŽC§p‰E¹†¶„VT!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/029be5e90913b19cf5890559cf3f98aa909f0a84 b/webm_parser/fuzzing/corpus/029be5e90913b19cf5890559cf3f98aa909f0a84 Binary files differnew file mode 100644 index 0000000..75abefb --- /dev/null +++ b/webm_parser/fuzzing/corpus/029be5e90913b19cf5890559cf3f98aa909f0a84 diff --git a/webm_parser/fuzzing/corpus/02fb96539b84bbd12de84ff05cbc9dc3faa96b7e b/webm_parser/fuzzing/corpus/02fb96539b84bbd12de84ff05cbc9dc3faa96b7e Binary files differnew file mode 100644 index 0000000..f6d3738 --- /dev/null +++ b/webm_parser/fuzzing/corpus/02fb96539b84bbd12de84ff05cbc9dc3faa96b7e diff --git a/webm_parser/fuzzing/corpus/0349f5632d21faa36b85520ad0b524d561f5e13f b/webm_parser/fuzzing/corpus/0349f5632d21faa36b85520ad0b524d561f5e13f Binary files differnew file mode 100644 index 0000000..f3df0a4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0349f5632d21faa36b85520ad0b524d561f5e13f diff --git a/webm_parser/fuzzing/corpus/036fc9daf7fb1b4274dd668cfd883248ebbad967 b/webm_parser/fuzzing/corpus/036fc9daf7fb1b4274dd668cfd883248ebbad967 new file mode 100644 index 0000000..5d97ec8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/036fc9daf7fb1b4274dd668cfd883248ebbad967 @@ -0,0 +1 @@ +S€gS»kŠ»ƒ³»ƒ³
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/037a4edc18e475ec81081e47277cbf51f1316680 b/webm_parser/fuzzing/corpus/037a4edc18e475ec81081e47277cbf51f1316680 Binary files differnew file mode 100644 index 0000000..3109c31 --- /dev/null +++ b/webm_parser/fuzzing/corpus/037a4edc18e475ec81081e47277cbf51f1316680 diff --git a/webm_parser/fuzzing/corpus/0481dad9a7d0e6fab0c703bba9b3268db96c6793 b/webm_parser/fuzzing/corpus/0481dad9a7d0e6fab0c703bba9b3268db96c6793 Binary files differnew file mode 100644 index 0000000..45d388a --- /dev/null +++ b/webm_parser/fuzzing/corpus/0481dad9a7d0e6fab0c703bba9b3268db96c6793 diff --git a/webm_parser/fuzzing/corpus/04dc2407e7142f5618aa5105377925b0b0ed544b b/webm_parser/fuzzing/corpus/04dc2407e7142f5618aa5105377925b0b0ed544b Binary files differnew file mode 100644 index 0000000..fa4ff2f --- /dev/null +++ b/webm_parser/fuzzing/corpus/04dc2407e7142f5618aa5105377925b0b0ed544b diff --git a/webm_parser/fuzzing/corpus/04fa2f34ff4a4406d136e5aaba5debe7d8129a06 b/webm_parser/fuzzing/corpus/04fa2f34ff4a4406d136e5aaba5debe7d8129a06 Binary files differnew file mode 100644 index 0000000..81b5bdd --- /dev/null +++ b/webm_parser/fuzzing/corpus/04fa2f34ff4a4406d136e5aaba5debe7d8129a06 diff --git a/webm_parser/fuzzing/corpus/054fd0041ad81cfad0a85e3c59195485492a4493 b/webm_parser/fuzzing/corpus/054fd0041ad81cfad0a85e3c59195485492a4493 Binary files differnew file mode 100644 index 0000000..e5ddccf --- /dev/null +++ b/webm_parser/fuzzing/corpus/054fd0041ad81cfad0a85e3c59195485492a4493 diff --git a/webm_parser/fuzzing/corpus/056b83ab2457979ea021e7118ab847eba265df15 b/webm_parser/fuzzing/corpus/056b83ab2457979ea021e7118ab847eba265df15 Binary files differnew file mode 100644 index 0000000..ef730c6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/056b83ab2457979ea021e7118ab847eba265df15 diff --git a/webm_parser/fuzzing/corpus/058326151c1d4a490964d495d35adcf15178030f b/webm_parser/fuzzing/corpus/058326151c1d4a490964d495d35adcf15178030f Binary files differnew file mode 100644 index 0000000..e6d60cc --- /dev/null +++ b/webm_parser/fuzzing/corpus/058326151c1d4a490964d495d35adcf15178030f diff --git a/webm_parser/fuzzing/corpus/05847b5be0eb200d6a6b340c939c7a654b55003e b/webm_parser/fuzzing/corpus/05847b5be0eb200d6a6b340c939c7a654b55003e Binary files differnew file mode 100644 index 0000000..c928212 --- /dev/null +++ b/webm_parser/fuzzing/corpus/05847b5be0eb200d6a6b340c939c7a654b55003e diff --git a/webm_parser/fuzzing/corpus/05b600ae9a9072ac2865247e69ed0736a0c58316 b/webm_parser/fuzzing/corpus/05b600ae9a9072ac2865247e69ed0736a0c58316 Binary files differnew file mode 100644 index 0000000..a39b94c --- /dev/null +++ b/webm_parser/fuzzing/corpus/05b600ae9a9072ac2865247e69ed0736a0c58316 diff --git a/webm_parser/fuzzing/corpus/05cf976698b55df1fcd03bc07446bb9283dad9d7 b/webm_parser/fuzzing/corpus/05cf976698b55df1fcd03bc07446bb9283dad9d7 new file mode 100644 index 0000000..effefb8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/05cf976698b55df1fcd03bc07446bb9283dad9d7 @@ -0,0 +1 @@ +S€gTÃg˜ss•gÈ’E£€Dz€D„€D‡€D…€gÈ€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/05d4dfda5e264fffda243f2991a86e96141f579a b/webm_parser/fuzzing/corpus/05d4dfda5e264fffda243f2991a86e96141f579a new file mode 100644 index 0000000..3a30835 --- /dev/null +++ b/webm_parser/fuzzing/corpus/05d4dfda5e264fffda243f2991a86e96141f579a @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gâ!S€g›T®k–®”m€‘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/061217fc0b6af0ec3851d9728e03ed9b30c702d5 b/webm_parser/fuzzing/corpus/061217fc0b6af0ec3851d9728e03ed9b30c702d5 new file mode 100644 index 0000000..d2dbb4d --- /dev/null +++ b/webm_parser/fuzzing/corpus/061217fc0b6af0ec3851d9728e03ed9b30c702d5 @@ -0,0 +1 @@ +u¢€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/077b53a7163e51c48e8cdaf4209bb837823ffba9 b/webm_parser/fuzzing/corpus/077b53a7163e51c48e8cdaf4209bb837823ffba9 new file mode 100644 index 0000000..f0a787c --- /dev/null +++ b/webm_parser/fuzzing/corpus/077b53a7163e51c48e8cdaf4209bb837823ffba9 @@ -0,0 +1 @@ +Tð€ð€€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/07c3ade9713892bb75db2d93b48ef40b262a54e6 b/webm_parser/fuzzing/corpus/07c3ade9713892bb75db2d93b48ef40b262a54e6 new file mode 100644 index 0000000..12d7edf --- /dev/null +++ b/webm_parser/fuzzing/corpus/07c3ade9713892bb75db2d93b48ef40b262a54e6 @@ -0,0 +1 @@ +S€gŒS»k‡»…·ƒñÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/07eaf3c7437032f60c905f6f8e3dfc193491b602 b/webm_parser/fuzzing/corpus/07eaf3c7437032f60c905f6f8e3dfc193491b602 new file mode 100644 index 0000000..5686078 --- /dev/null +++ b/webm_parser/fuzzing/corpus/07eaf3c7437032f60c905f6f8e3dfc193491b602 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠìƒÌ•ƒèÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/07f67b922b503354b2aebcdcc4ab7b6d3150b048 b/webm_parser/fuzzing/corpus/07f67b922b503354b2aebcdcc4ab7b6d3150b048 Binary files differnew file mode 100644 index 0000000..532f5d9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/07f67b922b503354b2aebcdcc4ab7b6d3150b048 diff --git a/webm_parser/fuzzing/corpus/08af8d91bb21835c50330e997d973cac8ff67766 b/webm_parser/fuzzing/corpus/08af8d91bb21835c50330e997d973cac8ff67766 Binary files differnew file mode 100644 index 0000000..7fab9be --- /dev/null +++ b/webm_parser/fuzzing/corpus/08af8d91bb21835c50330e997d973cac8ff67766 diff --git a/webm_parser/fuzzing/corpus/0a18f05bb16402756202160225aec9c5a654f1ae b/webm_parser/fuzzing/corpus/0a18f05bb16402756202160225aec9c5a654f1ae Binary files differnew file mode 100644 index 0000000..11f4d8c --- /dev/null +++ b/webm_parser/fuzzing/corpus/0a18f05bb16402756202160225aec9c5a654f1ae diff --git a/webm_parser/fuzzing/corpus/0b60823983971ee17a2590678f0fc0762c21bb73 b/webm_parser/fuzzing/corpus/0b60823983971ee17a2590678f0fc0762c21bb73 Binary files differnew file mode 100644 index 0000000..14fc6de --- /dev/null +++ b/webm_parser/fuzzing/corpus/0b60823983971ee17a2590678f0fc0762c21bb73 diff --git a/webm_parser/fuzzing/corpus/0bae7f0976af0f75974047b5f2cf4c9645412066 b/webm_parser/fuzzing/corpus/0bae7f0976af0f75974047b5f2cf4c9645412066 new file mode 100644 index 0000000..beeec12 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0bae7f0976af0f75974047b5f2cf4c9645412066 @@ -0,0 +1 @@ +S€gœC§p—E¹”¶’Ä€aT€€€€€€€€€€€€€ƒèÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/0c1862b4065eefab2535ecc6951295e38069a82e b/webm_parser/fuzzing/corpus/0c1862b4065eefab2535ecc6951295e38069a82e Binary files differnew file mode 100644 index 0000000..95342c8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0c1862b4065eefab2535ecc6951295e38069a82e diff --git a/webm_parser/fuzzing/corpus/0c2f51d5ffc69e69680bf3d6edb91d76c353ad14 b/webm_parser/fuzzing/corpus/0c2f51d5ffc69e69680bf3d6edb91d76c353ad14 new file mode 100644 index 0000000..f0ad094 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0c2f51d5ffc69e69680bf3d6edb91d76c353ad14 @@ -0,0 +1 @@ +S€g€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/0c3af72d69f18103383c9cd41a7f2676a9d28a40 b/webm_parser/fuzzing/corpus/0c3af72d69f18103383c9cd41a7f2676a9d28a40 Binary files differnew file mode 100644 index 0000000..74f0550 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0c3af72d69f18103383c9cd41a7f2676a9d28a40 diff --git a/webm_parser/fuzzing/corpus/0c43df7fc9d06187249187583c3c082520701289 b/webm_parser/fuzzing/corpus/0c43df7fc9d06187249187583c3c082520701289 new file mode 100644 index 0000000..c0033c4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0c43df7fc9d06187249187583c3c082520701289 @@ -0,0 +1 @@ +S€gŒC¶u‡ …ûƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/0cadf5ecf58a394560a1f6db72a2e21264445d13 b/webm_parser/fuzzing/corpus/0cadf5ecf58a394560a1f6db72a2e21264445d13 new file mode 100644 index 0000000..e3e6491 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0cadf5ecf58a394560a1f6db72a2e21264445d13 @@ -0,0 +1 @@ +S€g™T®k”®’m€b@ŒP5‰á€Gç€Gç€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/0cd91a4e7bec912beb3b47a685b89d5be24d0046 b/webm_parser/fuzzing/corpus/0cd91a4e7bec912beb3b47a685b89d5be24d0046 new file mode 100644 index 0000000..33d3668 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0cd91a4e7bec912beb3b47a685b89d5be24d0046 @@ -0,0 +1 @@ +S€gŽS»k‰»‡·…²ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/0d0da60f91f9489af113d8484d9b6871622523d5 b/webm_parser/fuzzing/corpus/0d0da60f91f9489af113d8484d9b6871622523d5 new file mode 100644 index 0000000..14e966c --- /dev/null +++ b/webm_parser/fuzzing/corpus/0d0da60f91f9489af113d8484d9b6871622523d5 @@ -0,0 +1 @@ +S€g”T®i®m€‚b@‡P5„Gâ!S€gŠT®k…®ƒ¹
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/0d30a4f88e53de1ce4bf1ec724016f2f4c11bc9b b/webm_parser/fuzzing/corpus/0d30a4f88e53de1ce4bf1ec724016f2f4c11bc9b Binary files differnew file mode 100644 index 0000000..2de1dab --- /dev/null +++ b/webm_parser/fuzzing/corpus/0d30a4f88e53de1ce4bf1ec724016f2f4c11bc9b diff --git a/webm_parser/fuzzing/corpus/0d6b3b1d024e7aa73fbc58b157ab53df048a2f2c b/webm_parser/fuzzing/corpus/0d6b3b1d024e7aa73fbc58b157ab53df048a2f2c Binary files differnew file mode 100644 index 0000000..1a5dc0e --- /dev/null +++ b/webm_parser/fuzzing/corpus/0d6b3b1d024e7aa73fbc58b157ab53df048a2f2c diff --git a/webm_parser/fuzzing/corpus/0d94310cfd8a9acdbc7fb82ad9c73cdf3f64f926 b/webm_parser/fuzzing/corpus/0d94310cfd8a9acdbc7fb82ad9c73cdf3f64f926 Binary files differnew file mode 100644 index 0000000..676e417 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0d94310cfd8a9acdbc7fb82ad9c73cdf3f64f926 diff --git a/webm_parser/fuzzing/corpus/0f5741483be8f4f6eddcb70ea418fb0c08da9443 b/webm_parser/fuzzing/corpus/0f5741483be8f4f6eddcb70ea418fb0c08da9443 Binary files differnew file mode 100644 index 0000000..34838b0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/0f5741483be8f4f6eddcb70ea418fb0c08da9443 diff --git a/webm_parser/fuzzing/corpus/109e125f729f84d69c3e3a614123547a094534ad b/webm_parser/fuzzing/corpus/109e125f729f84d69c3e3a614123547a094534ad new file mode 100644 index 0000000..841b067 --- /dev/null +++ b/webm_parser/fuzzing/corpus/109e125f729f84d69c3e3a614123547a094534ad @@ -0,0 +1 @@ +S€g”C¶u u¡Š¦ƒî¦ƒî
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/10d257a4a314e20644fbc469cdebafe16c7f46a8 b/webm_parser/fuzzing/corpus/10d257a4a314e20644fbc469cdebafe16c7f46a8 new file mode 100644 index 0000000..edb0d4f --- /dev/null +++ b/webm_parser/fuzzing/corpus/10d257a4a314e20644fbc469cdebafe16c7f46a8 @@ -0,0 +1 @@ +S€gTÃgŠss‡gÈ„D„ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/10d951f88995a2176878501a2633b9bb4822ff96 b/webm_parser/fuzzing/corpus/10d951f88995a2176878501a2633b9bb4822ff96 new file mode 100644 index 0000000..ca0a094 --- /dev/null +++ b/webm_parser/fuzzing/corpus/10d951f88995a2176878501a2633b9bb4822ff96 @@ -0,0 +1 @@ +!EߣˆB‚@webmS€g£M›t€I©f€C¶u€TS€gŒT®k‡®…®k€Sჟ»k€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/111b8f8318a269bbe0f7d9f1cebfdc1cb43c982b b/webm_parser/fuzzing/corpus/111b8f8318a269bbe0f7d9f1cebfdc1cb43c982b new file mode 100644 index 0000000..a612d8b --- /dev/null +++ b/webm_parser/fuzzing/corpus/111b8f8318a269bbe0f7d9f1cebfdc1cb43c982b @@ -0,0 +1 @@ +(Á
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/11c122ce1f7d993f809a4eb5db17c738f279a707 b/webm_parser/fuzzing/corpus/11c122ce1f7d993f809a4eb5db17c738f279a707 Binary files differnew file mode 100644 index 0000000..064086e --- /dev/null +++ b/webm_parser/fuzzing/corpus/11c122ce1f7d993f809a4eb5db17c738f279a707 diff --git a/webm_parser/fuzzing/corpus/11e288617056809208561f2a9112fd0664d378d2 b/webm_parser/fuzzing/corpus/11e288617056809208561f2a9112fd0664d378d2 new file mode 100644 index 0000000..9e2b9d3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/11e288617056809208561f2a9112fd0664d378d2 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gâ!S€gŠT®k…®ƒ¹ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/124fde9cfe7157773d8febcbb0829914bf4d17d3 b/webm_parser/fuzzing/corpus/124fde9cfe7157773d8febcbb0829914bf4d17d3 Binary files differnew file mode 100644 index 0000000..1a1a2df --- /dev/null +++ b/webm_parser/fuzzing/corpus/124fde9cfe7157773d8febcbb0829914bf4d17d3 diff --git a/webm_parser/fuzzing/corpus/1386cc740c80ede6dfea5f3bb1d4fe1501ab5e91 b/webm_parser/fuzzing/corpus/1386cc740c80ede6dfea5f3bb1d4fe1501ab5e91 new file mode 100644 index 0000000..f1b13fc --- /dev/null +++ b/webm_parser/fuzzing/corpus/1386cc740c80ede6dfea5f3bb1d4fe1501ab5e91 @@ -0,0 +1 @@ +S€gŒT®k‡®…%†ˆ!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/13dd373ccb0c534402c6f7c4a0bdd723a26bc2c1 b/webm_parser/fuzzing/corpus/13dd373ccb0c534402c6f7c4a0bdd723a26bc2c1 new file mode 100644 index 0000000..5fea349 --- /dev/null +++ b/webm_parser/fuzzing/corpus/13dd373ccb0c534402c6f7c4a0bdd723a26bc2c1 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gá
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/13fc3d2b32d789c84be6349bb585c308289b4c3e b/webm_parser/fuzzing/corpus/13fc3d2b32d789c84be6349bb585c308289b4c3e new file mode 100644 index 0000000..94ff769 --- /dev/null +++ b/webm_parser/fuzzing/corpus/13fc3d2b32d789c84be6349bb585c308289b4c3e @@ -0,0 +1 @@ +S€g‘TÃgŒss‰gȆD„ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/148357130d1e5ac4059ad2bb6c63d78e2523f7d2 b/webm_parser/fuzzing/corpus/148357130d1e5ac4059ad2bb6c63d78e2523f7d2 Binary files differnew file mode 100644 index 0000000..23129b8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/148357130d1e5ac4059ad2bb6c63d78e2523f7d2 diff --git a/webm_parser/fuzzing/corpus/15366a3aafe2590c2e3183c088dde4cc100cb956 b/webm_parser/fuzzing/corpus/15366a3aafe2590c2e3183c088dde4cc100cb956 new file mode 100644 index 0000000..acb4e59 --- /dev/null +++ b/webm_parser/fuzzing/corpus/15366a3aafe2590c2e3183c088dde4cc100cb956 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶³sÄVTA‘@…@B€@”@C¶@sĶ@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/15feeb939fa90b25f57622a0abd9155ca88ad08b b/webm_parser/fuzzing/corpus/15feeb939fa90b25f57622a0abd9155ca88ad08b Binary files differnew file mode 100644 index 0000000..e8c0a65 --- /dev/null +++ b/webm_parser/fuzzing/corpus/15feeb939fa90b25f57622a0abd9155ca88ad08b diff --git a/webm_parser/fuzzing/corpus/16282b78a2018eb78544316554a92fe1003859e3 b/webm_parser/fuzzing/corpus/16282b78a2018eb78544316554a92fe1003859e3 new file mode 100644 index 0000000..4d9e8d5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/16282b78a2018eb78544316554a92fe1003859e3 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gâ!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1717628a6d6ea868febec5fb196edcf4b9eb284d b/webm_parser/fuzzing/corpus/1717628a6d6ea868febec5fb196edcf4b9eb284d new file mode 100644 index 0000000..a495a9d --- /dev/null +++ b/webm_parser/fuzzing/corpus/1717628a6d6ea868febec5fb196edcf4b9eb284d @@ -0,0 +1 @@ +S€gŒT®k‡®…ჟÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/17921b1e28600e7e3faf67fc68b397ecab4e2a52 b/webm_parser/fuzzing/corpus/17921b1e28600e7e3faf67fc68b397ecab4e2a52 new file mode 100644 index 0000000..a351f0e --- /dev/null +++ b/webm_parser/fuzzing/corpus/17921b1e28600e7e3faf67fc68b397ecab4e2a52 @@ -0,0 +1 @@ +S€gC§pŠE¹‡¶…€ƒ…!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/17b8276355dd2368647b7756431820f5275cc3e2 b/webm_parser/fuzzing/corpus/17b8276355dd2368647b7756431820f5275cc3e2 Binary files differnew file mode 100644 index 0000000..0aeaa9e --- /dev/null +++ b/webm_parser/fuzzing/corpus/17b8276355dd2368647b7756431820f5275cc3e2 diff --git a/webm_parser/fuzzing/corpus/17d0aece97973ab23a467486b177ea9722e1b90b b/webm_parser/fuzzing/corpus/17d0aece97973ab23a467486b177ea9722e1b90b new file mode 100644 index 0000000..447e515 --- /dev/null +++ b/webm_parser/fuzzing/corpus/17d0aece97973ab23a467486b177ea9722e1b90b @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶“|°…@hello€€€€€€€€€…@B€@…@C¶@
sÄ€€€€€€„€–€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/17e80cf8c247d17acad56c88750da34893fcb4fd b/webm_parser/fuzzing/corpus/17e80cf8c247d17acad56c88750da34893fcb4fd Binary files differnew file mode 100644 index 0000000..23ff05e --- /dev/null +++ b/webm_parser/fuzzing/corpus/17e80cf8c247d17acad56c88750da34893fcb4fd diff --git a/webm_parser/fuzzing/corpus/18040e106688eb1d54323927a403439ecfde6626 b/webm_parser/fuzzing/corpus/18040e106688eb1d54323927a403439ecfde6626 new file mode 100644 index 0000000..14b59f5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/18040e106688eb1d54323927a403439ecfde6626 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gâ!S€g”T®k®m€Šb@‡P5„Gâ!S€g›T®k–®”m€;S€g›T®k–®”m€‘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1813261b3141faa01431c82e06292485516d3327 b/webm_parser/fuzzing/corpus/1813261b3141faa01431c82e06292485516d3327 new file mode 100644 index 0000000..063dacd --- /dev/null +++ b/webm_parser/fuzzing/corpus/1813261b3141faa01431c82e06292485516d3327 @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶²€ …@helloC|…lang0C~…area0C}…lang€…S|1C–T®k‘®m€Œb@‰P2€ÿ†G*lang2C~ƒGè…€ara1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/184ee2343d4b9f62c69383692829fd852ad8855d b/webm_parser/fuzzing/corpus/184ee2343d4b9f62c69383692829fd852ad8855d new file mode 100644 index 0000000..10ba100 --- /dev/null +++ b/webm_parser/fuzzing/corpus/184ee2343d4b9f62c69383692829fd852ad8855d @@ -0,0 +1 @@ +Tª€g‰C¶u„ ‚¢€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/186035a2f5c09ccd6b7d15de46f8561c98d8897d b/webm_parser/fuzzing/corpus/186035a2f5c09ccd6b7d15de46f8561c98d8897d new file mode 100644 index 0000000..fdcc0a0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/186035a2f5c09ccd6b7d15de46f8561c98d8897d @@ -0,0 +1 @@ +S€gC§pˆE¹…¶ƒ‘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/186292900ad9d43881b71765158c32d78d80c8f3 b/webm_parser/fuzzing/corpus/186292900ad9d43881b71765158c32d78d80c8f3 Binary files differnew file mode 100644 index 0000000..35ada6f --- /dev/null +++ b/webm_parser/fuzzing/corpus/186292900ad9d43881b71765158c32d78d80c8f3 diff --git a/webm_parser/fuzzing/corpus/18fd762b91406d37b85a7b342a91434a15f17290 b/webm_parser/fuzzing/corpus/18fd762b91406d37b85a7b342a91434a15f17290 Binary files differnew file mode 100644 index 0000000..26c44c9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/18fd762b91406d37b85a7b342a91434a15f17290 diff --git a/webm_parser/fuzzing/corpus/19334eee05eeb18c549498e7ca2e792a2a4e04f3 b/webm_parser/fuzzing/corpus/19334eee05eeb18c549498e7ca2e792a2a4e04f3 new file mode 100644 index 0000000..cc25e67 --- /dev/null +++ b/webm_parser/fuzzing/corpus/19334eee05eeb18c549498e7ca2e792a2a4e04f3 @@ -0,0 +1 @@ +S€gC§p‹E¹ˆ¶†€„C|
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1a9a5df35779dd6e9e1de171b9bb0d316d2b64a5 b/webm_parser/fuzzing/corpus/1a9a5df35779dd6e9e1de171b9bb0d316d2b64a5 new file mode 100644 index 0000000..7293974 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1a9a5df35779dd6e9e1de171b9bb0d316d2b64a5 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šàˆ#ƒã„@Û
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1ab3d30f60743c2a1d3043773aae3a04f83c07c0 b/webm_parser/fuzzing/corpus/1ab3d30f60743c2a1d3043773aae3a04f83c07c0 new file mode 100644 index 0000000..9682a0f --- /dev/null +++ b/webm_parser/fuzzing/corpus/1ab3d30f60743c2a1d3043773aae3a04f83c07c0 @@ -0,0 +1 @@ +Sg€„IŒû@D‰ÉÛ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1ad84ed46f3fda305150bac93958a5a390e23a67 b/webm_parser/fuzzing/corpus/1ad84ed46f3fda305150bac93958a5a390e23a67 new file mode 100644 index 0000000..7e0f66f --- /dev/null +++ b/webm_parser/fuzzing/corpus/1ad84ed46f3fda305150bac93958a5a390e23a67 @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶²€°…@helloC|…lang0C|…area0C|…lang1C|…lang2C|…area1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1b40a997150aa03c23ecc6efe445a2d7c3dd8368 b/webm_parser/fuzzing/corpus/1b40a997150aa03c23ecc6efe445a2d7c3dd8368 Binary files differnew file mode 100644 index 0000000..b5d9ab5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1b40a997150aa03c23ecc6efe445a2d7c3dd8368 diff --git a/webm_parser/fuzzing/corpus/1b6b3bab9032cd420f350b6bb252942484f6b527 b/webm_parser/fuzzing/corpus/1b6b3bab9032cd420f350b6bb252942484f6b527 new file mode 100644 index 0000000..5275f03 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1b6b3bab9032cd420f350b6bb252942484f6b527 @@ -0,0 +1 @@ +bdT°gŒT®‡®…ᇟ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1b7765cb05c94581461ffcd38d38b7b272d8e9d6 b/webm_parser/fuzzing/corpus/1b7765cb05c94581461ffcd38d38b7b272d8e9d6 new file mode 100644 index 0000000..980ae84 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1b7765cb05c94581461ffcd38d38b7b272d8e9d6 @@ -0,0 +1 @@ +S€gT®kˆ®†à„T²
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1b89e3fe0cbd4c2291a74bf21969a9d9d851cbf1 b/webm_parser/fuzzing/corpus/1b89e3fe0cbd4c2291a74bf21969a9d9d851cbf1 new file mode 100644 index 0000000..24ed722 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1b89e3fe0cbd4c2291a74bf21969a9d9d851cbf1 @@ -0,0 +1 @@ +ã
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1b95d4da08fe949a60f63cd59213d42d30dbd381 b/webm_parser/fuzzing/corpus/1b95d4da08fe949a60f63cd59213d42d30dbd381 new file mode 100644 index 0000000..1326dfe --- /dev/null +++ b/webm_parser/fuzzing/corpus/1b95d4da08fe949a60f63cd59213d42d30dbd381 @@ -0,0 +1 @@ +S€gŒS»k‡»…·ƒ²
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1bac1200e05bb3269d019903241791c917a12bd0 b/webm_parser/fuzzing/corpus/1bac1200e05bb3269d019903241791c917a12bd0 Binary files differnew file mode 100644 index 0000000..a930622 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1bac1200e05bb3269d019903241791c917a12bd0 diff --git a/webm_parser/fuzzing/corpus/1bb8f5f81b7f6c1d58e1f7bb13fa561fe1a146bc b/webm_parser/fuzzing/corpus/1bb8f5f81b7f6c1d58e1f7bb13fa561fe1a146bc new file mode 100644 index 0000000..b3204bc --- /dev/null +++ b/webm_parser/fuzzing/corpus/1bb8f5f81b7f6c1d58e1f7bb13fa561fe1a146bc @@ -0,0 +1 @@ +S€g‘C¶uŒ Šu¡‡¦…îƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1c56068c6dd17e9a824db6da78d64f933267a8c0 b/webm_parser/fuzzing/corpus/1c56068c6dd17e9a824db6da78d64f933267a8c0 new file mode 100644 index 0000000..bd3b28e --- /dev/null +++ b/webm_parser/fuzzing/corpus/1c56068c6dd17e9a824db6da78d64f933267a8c0 @@ -0,0 +1 @@ +S€gTÃgŠss‡cÀ„hgÈ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1c8db8b9d88dd3483b6f81e4224c4f985046e6ac b/webm_parser/fuzzing/corpus/1c8db8b9d88dd3483b6f81e4224c4f985046e6ac Binary files differnew file mode 100644 index 0000000..57d9844 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1c8db8b9d88dd3483b6f81e4224c4f985046e6ac diff --git a/webm_parser/fuzzing/corpus/1d29a77924602a79c0f546535a885f59cbbbc405 b/webm_parser/fuzzing/corpus/1d29a77924602a79c0f546535a885f59cbbbc405 new file mode 100644 index 0000000..3baa1ce --- /dev/null +++ b/webm_parser/fuzzing/corpus/1d29a77924602a79c0f546535a885f59cbbbc405 @@ -0,0 +1 @@ +S€g£I©f€I©f€I©f€C¶u€T®k€)S»k€ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1d75a65733da627e5c401625bff522eadf098315 b/webm_parser/fuzzing/corpus/1d75a65733da627e5c401625bff522eadf098315 new file mode 100644 index 0000000..5b6200c --- /dev/null +++ b/webm_parser/fuzzing/corpus/1d75a65733da627e5c401625bff522eadf098315 @@ -0,0 +1 @@ +S€gT®kˆ®†à„S¸ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/1df9507cc2a54a369646d6d34d846d3fcc172479 b/webm_parser/fuzzing/corpus/1df9507cc2a54a369646d6d34d846d3fcc172479 Binary files differnew file mode 100644 index 0000000..0826b42 --- /dev/null +++ b/webm_parser/fuzzing/corpus/1df9507cc2a54a369646d6d34d846d3fcc172479 diff --git a/webm_parser/fuzzing/corpus/1e7a571be5aa542c3dfec1223d2e089a3075ce1f b/webm_parser/fuzzing/corpus/1e7a571be5aa542c3dfec1223d2e089a3075ce1f new file mode 100644 index 0000000..4feebdf --- /dev/null +++ b/webm_parser/fuzzing/corpus/1e7a571be5aa542c3dfec1223d2e089a3075ce1f @@ -0,0 +1 @@ +S€gŒT®k‡®…ჟ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/20a6b040258fbfa09bb37c6fc07106b2e43bada7 b/webm_parser/fuzzing/corpus/20a6b040258fbfa09bb37c6fc07106b2e43bada7 new file mode 100644 index 0000000..fcae8ab --- /dev/null +++ b/webm_parser/fuzzing/corpus/20a6b040258fbfa09bb37c6fc07106b2e43bada7 @@ -0,0 +1 @@ +S€gŠS»k…»ƒ³ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/20e8ca854d3c0c375dc943142a04ee2260f0d1fd b/webm_parser/fuzzing/corpus/20e8ca854d3c0c375dc943142a04ee2260f0d1fd Binary files differnew file mode 100644 index 0000000..dbaa571 --- /dev/null +++ b/webm_parser/fuzzing/corpus/20e8ca854d3c0c375dc943142a04ee2260f0d1fd diff --git a/webm_parser/fuzzing/corpus/212ee7d21a8cb25249644cab4f959db111f3a3c0 b/webm_parser/fuzzing/corpus/212ee7d21a8cb25249644cab4f959db111f3a3c0 Binary files differnew file mode 100644 index 0000000..0474655 --- /dev/null +++ b/webm_parser/fuzzing/corpus/212ee7d21a8cb25249644cab4f959db111f3a3c0 diff --git a/webm_parser/fuzzing/corpus/224ce7b415f7950118880fce0594734aef489fec b/webm_parser/fuzzing/corpus/224ce7b415f7950118880fce0594734aef489fec Binary files differnew file mode 100644 index 0000000..35aedfe --- /dev/null +++ b/webm_parser/fuzzing/corpus/224ce7b415f7950118880fce0594734aef489fec diff --git a/webm_parser/fuzzing/corpus/226e70c1beddace83862791a6555c80475640bd0 b/webm_parser/fuzzing/corpus/226e70c1beddace83862791a6555c80475640bd0 Binary files differnew file mode 100644 index 0000000..028ff9d --- /dev/null +++ b/webm_parser/fuzzing/corpus/226e70c1beddace83862791a6555c80475640bd0 diff --git a/webm_parser/fuzzing/corpus/233d53e3eb21b6ea6feebd6e59e3ef888a840f49 b/webm_parser/fuzzing/corpus/233d53e3eb21b6ea6feebd6e59e3ef888a840f49 Binary files differnew file mode 100644 index 0000000..ff5a962 --- /dev/null +++ b/webm_parser/fuzzing/corpus/233d53e3eb21b6ea6feebd6e59e3ef888a840f49 diff --git a/webm_parser/fuzzing/corpus/237f7aee90f206d3a6b138cc908b8921b544e190 b/webm_parser/fuzzing/corpus/237f7aee90f206d3a6b138cc908b8921b544e190 Binary files differnew file mode 100644 index 0000000..60d1eaa --- /dev/null +++ b/webm_parser/fuzzing/corpus/237f7aee90f206d3a6b138cc908b8921b544e190 diff --git a/webm_parser/fuzzing/corpus/23e1ac0f77f1283cf6e9fa044df3ec51bff27fd4 b/webm_parser/fuzzing/corpus/23e1ac0f77f1283cf6e9fa044df3ec51bff27fd4 new file mode 100644 index 0000000..b17cd82 --- /dev/null +++ b/webm_parser/fuzzing/corpus/23e1ac0f77f1283cf6e9fa044df3ec51bff27fd4 @@ -0,0 +1 @@ +S€gŠS»k…»ƒ³
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/23fcee6c71a8f5a22447df688724c0250fb77a70 b/webm_parser/fuzzing/corpus/23fcee6c71a8f5a22447df688724c0250fb77a70 Binary files differnew file mode 100644 index 0000000..6580bec --- /dev/null +++ b/webm_parser/fuzzing/corpus/23fcee6c71a8f5a22447df688724c0250fb77a70 diff --git a/webm_parser/fuzzing/corpus/2459eb855d8c6ebac13cb74d996565d78130f4dd b/webm_parser/fuzzing/corpus/2459eb855d8c6ebac13cb74d996565d78130f4dd new file mode 100644 index 0000000..2ad814f --- /dev/null +++ b/webm_parser/fuzzing/corpus/2459eb855d8c6ebac13cb74d996565d78130f4dd @@ -0,0 +1 @@ +EߣˆB‚@webmS€g©C¶u¤ç@«@£…¾Eß#Z
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/259188e5fb0c09046df96f6d565c59e0d146f198 b/webm_parser/fuzzing/corpus/259188e5fb0c09046df96f6d565c59e0d146f198 new file mode 100644 index 0000000..c69951e --- /dev/null +++ b/webm_parser/fuzzing/corpus/259188e5fb0c09046df96f6d565c59e0d146f198 @@ -0,0 +1 @@ +S€gŠT®k…®ƒœ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/26f07f8e28e7521ed282fe5c3938c1ae225816b9 b/webm_parser/fuzzing/corpus/26f07f8e28e7521ed282fe5c3938c1ae225816b9 new file mode 100644 index 0000000..d592c6e --- /dev/null +++ b/webm_parser/fuzzing/corpus/26f07f8e28e7521ed282fe5c3938c1ae225816b9 @@ -0,0 +1 @@ +STŠggÀss‡g„D…!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/276425d65d58453d03a3444c9f6662d08d449af7 b/webm_parser/fuzzing/corpus/276425d65d58453d03a3444c9f6662d08d449af7 new file mode 100644 index 0000000..3d99d50 --- /dev/null +++ b/webm_parser/fuzzing/corpus/276425d65d58453d03a3444c9f6662d08d449af7 @@ -0,0 +1 @@ +S€g”TÃgssŒcÀ‰hÊ€cÊ€cÅ€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/27d62874ec87a2552e7c842da65de113aa69f7aa b/webm_parser/fuzzing/corpus/27d62874ec87a2552e7c842da65de113aa69f7aa Binary files differnew file mode 100644 index 0000000..f83242a --- /dev/null +++ b/webm_parser/fuzzing/corpus/27d62874ec87a2552e7c842da65de113aa69f7aa diff --git a/webm_parser/fuzzing/corpus/281b259280ada5d07b07a22cbe9a78c7b0fba94b b/webm_parser/fuzzing/corpus/281b259280ada5d07b07a22cbe9a78c7b0fba94b new file mode 100644 index 0000000..d59afe9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/281b259280ada5d07b07a22cbe9a78c7b0fba94b @@ -0,0 +1 @@ +S€g”C¶u u¡Š¦ƒî¦ƒî
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/299ed12b98673c6c4adbcf886cb70db8adde6aa1 b/webm_parser/fuzzing/corpus/299ed12b98673c6c4adbcf886cb70db8adde6aa1 new file mode 100644 index 0000000..2afc785 --- /dev/null +++ b/webm_parser/fuzzing/corpus/299ed12b98673c6c4adbcf886cb70db8adde6aa1 @@ -0,0 +1 @@ +S€g–T®k‘®m€Œb@‰P3†GçƒGè€S€gg—T®kÀ®m€b@ŠP3‡Gç„Gè
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2a40feb7480d0b31c36d5626761e948d0ae52792 b/webm_parser/fuzzing/corpus/2a40feb7480d0b31c36d5626761e948d0ae52792 Binary files differnew file mode 100644 index 0000000..6828d16 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2a40feb7480d0b31c36d5626761e948d0ae52792 diff --git a/webm_parser/fuzzing/corpus/2a6d7e2b829ed28307b551eda0d96f1834bff899 b/webm_parser/fuzzing/corpus/2a6d7e2b829ed28307b551eda0d96f1834bff899 new file mode 100644 index 0000000..6556c80 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2a6d7e2b829ed28307b551eda0d96f1834bff899 @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶²€°…@helltC|…lang0C~…area0C|…lang1C|…lang2C~…a1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2a8dda90aa286175b5c683b57fae1dc7e6ac1e7e b/webm_parser/fuzzing/corpus/2a8dda90aa286175b5c683b57fae1dc7e6ac1e7e Binary files differnew file mode 100644 index 0000000..f453884 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2a8dda90aa286175b5c683b57fae1dc7e6ac1e7e diff --git a/webm_parser/fuzzing/corpus/2a9588e6fab82016b545462cff2ed014c0345551 b/webm_parser/fuzzing/corpus/2a9588e6fab82016b545462cff2ed014c0345551 new file mode 100644 index 0000000..0140636 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2a9588e6fab82016b545462cff2ed014c0345551 @@ -0,0 +1 @@ +S€gT®kˆ®†à„T°ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2b1d57a8e8fa7164c9ba00957c9486010754560c b/webm_parser/fuzzing/corpus/2b1d57a8e8fa7164c9ba00957c9486010754560c new file mode 100644 index 0000000..298598f --- /dev/null +++ b/webm_parser/fuzzing/corpus/2b1d57a8e8fa7164c9ba00957c9486010754560c @@ -0,0 +1 @@ +S€g–T®k‘®m€Œb@‰P5†GçƒGè€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2b475d1a8f2fe4fbba92e1424f0ea96d95b12fb7 b/webm_parser/fuzzing/corpus/2b475d1a8f2fe4fbba92e1424f0ea96d95b12fb7 new file mode 100644 index 0000000..7e8be98 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2b475d1a8f2fe4fbba92e1424f0ea96d95b12fb7 @@ -0,0 +1 @@ +S€g˜T®k“®‘m€Žb@„P1b@€P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2bb80c6d0e2eadd73018eb2a8cae37d41602ee79 b/webm_parser/fuzzing/corpus/2bb80c6d0e2eadd73018eb2a8cae37d41602ee79 Binary files differnew file mode 100644 index 0000000..8f07d6c --- /dev/null +++ b/webm_parser/fuzzing/corpus/2bb80c6d0e2eadd73018eb2a8cae37d41602ee79 diff --git a/webm_parser/fuzzing/corpus/2c1389f882e256e260e37e8a67af7e32d0f4e0fc b/webm_parser/fuzzing/corpus/2c1389f882e256e260e37e8a67af7e32d0f4e0fc new file mode 100644 index 0000000..6710408 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2c1389f882e256e260e37e8a67af7e32d0f4e0fc @@ -0,0 +1 @@ +µÿÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2c1f94c76e4eec607cce5311323620f349912e73 b/webm_parser/fuzzing/corpus/2c1f94c76e4eec607cce5311323620f349912e73 new file mode 100644 index 0000000..26995d2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2c1f94c76e4eec607cce5311323620f349912e73 @@ -0,0 +1 @@ +S€gŽS»k‰»‡·…ðƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2c962c7fbceaacf8247ac9b70c8eeb1f814e435b b/webm_parser/fuzzing/corpus/2c962c7fbceaacf8247ac9b70c8eeb1f814e435b new file mode 100644 index 0000000..856cb5b --- /dev/null +++ b/webm_parser/fuzzing/corpus/2c962c7fbceaacf8247ac9b70c8eeb1f814e435b @@ -0,0 +1 @@ +S€gŒM›t‡M»„S¬ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2c9aaabacf3a3b48dec4a85767cc5d38a1736aff b/webm_parser/fuzzing/corpus/2c9aaabacf3a3b48dec4a85767cc5d38a1736aff new file mode 100644 index 0000000..28edff6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2c9aaabacf3a3b48dec4a85767cc5d38a1736aff @@ -0,0 +1 @@ +S€gC¶uˆ †Ž„y‚Ì€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2cf086299f983d0afc7f95c5e0c86b657448af33 b/webm_parser/fuzzing/corpus/2cf086299f983d0afc7f95c5e0c86b657448af33 Binary files differnew file mode 100644 index 0000000..13f1826 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2cf086299f983d0afc7f95c5e0c86b657448af33 diff --git a/webm_parser/fuzzing/corpus/2d0d96b95e9a3316d1ff0ef019ada509bbde4c9d b/webm_parser/fuzzing/corpus/2d0d96b95e9a3316d1ff0ef019ada509bbde4c9d Binary files differnew file mode 100644 index 0000000..adcb9e1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2d0d96b95e9a3316d1ff0ef019ada509bbde4c9d diff --git a/webm_parser/fuzzing/corpus/2d414d5dfd20055393df3208009840da9cfabca9 b/webm_parser/fuzzing/corpus/2d414d5dfd20055393df3208009840da9cfabca9 Binary files differnew file mode 100644 index 0000000..8e5d23f --- /dev/null +++ b/webm_parser/fuzzing/corpus/2d414d5dfd20055393df3208009840da9cfabca9 diff --git a/webm_parser/fuzzing/corpus/2df967edcc00ac5c8e00037f1a43680600af5cba b/webm_parser/fuzzing/corpus/2df967edcc00ac5c8e00037f1a43680600af5cba Binary files differnew file mode 100644 index 0000000..fb0e9b1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2df967edcc00ac5c8e00037f1a43680600af5cba diff --git a/webm_parser/fuzzing/corpus/2e6b09f1eca05ce2bba920fb78f9bca2a883fda0 b/webm_parser/fuzzing/corpus/2e6b09f1eca05ce2bba920fb78f9bca2a883fda0 new file mode 100644 index 0000000..2a3a9d3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2e6b09f1eca05ce2bba920fb78f9bca2a883fda0 @@ -0,0 +1 @@ +Eߣ„B‚!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/2e8bde3549723e13849b604f4deedd51c71dfef5 b/webm_parser/fuzzing/corpus/2e8bde3549723e13849b604f4deedd51c71dfef5 Binary files differnew file mode 100644 index 0000000..16bf112 --- /dev/null +++ b/webm_parser/fuzzing/corpus/2e8bde3549723e13849b604f4deedd51c71dfef5 diff --git a/webm_parser/fuzzing/corpus/2f6e92a71918d01c16762d5ca59b328f1341d326 b/webm_parser/fuzzing/corpus/2f6e92a71918d01c16762d5ca59b328f1341d326 new file mode 100644 index 0000000..3393c8d --- /dev/null +++ b/webm_parser/fuzzing/corpus/2f6e92a71918d01c16762d5ca59b328f1341d326 @@ -0,0 +1 @@ +S€g»k
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/306e2cf9aebe012cb0769b1b2a6ea68af2e8ed44 b/webm_parser/fuzzing/corpus/306e2cf9aebe012cb0769b1b2a6ea68af2e8ed44 new file mode 100644 index 0000000..60a0f48 --- /dev/null +++ b/webm_parser/fuzzing/corpus/306e2cf9aebe012cb0769b1b2a6ea68af2e8ed44 @@ -0,0 +1 @@ +áEߣˆB‚@webmS€g£*M'›t€I©f€C¶u€TSkk»€®€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/30f7348d35de0c47d2044736cb115972b800569d b/webm_parser/fuzzing/corpus/30f7348d35de0c47d2044736cb115972b800569d Binary files differnew file mode 100644 index 0000000..d992dbe --- /dev/null +++ b/webm_parser/fuzzing/corpus/30f7348d35de0c47d2044736cb115972b800569d diff --git a/webm_parser/fuzzing/corpus/310798b8d94a3a2fe649a4c871458b4d74fdcf32 b/webm_parser/fuzzing/corpus/310798b8d94a3a2fe649a4c871458b4d74fdcf32 new file mode 100644 index 0000000..e3c32aa --- /dev/null +++ b/webm_parser/fuzzing/corpus/310798b8d94a3a2fe649a4c871458b4d74fdcf32 @@ -0,0 +1 @@ +S€gC§pˆE¹…¶ƒ’
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/31ea606a9859bb29d7f98162d254348021e0d932 b/webm_parser/fuzzing/corpus/31ea606a9859bb29d7f98162d254348021e0d932 new file mode 100644 index 0000000..038f1a9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/31ea606a9859bb29d7f98162d254348021e0d932 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶³sÄVTA‘@’@€@…@B€@…@C¶@sĶ@€ÿÿÿ¶@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3230fc31ab8d408c484aa28bbe36431f71101243 b/webm_parser/fuzzing/corpus/3230fc31ab8d408c484aa28bbe36431f71101243 Binary files differnew file mode 100644 index 0000000..cad0aa0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3230fc31ab8d408c484aa28bbe36431f71101243 diff --git a/webm_parser/fuzzing/corpus/328b7c92996e480d1f11efe847c3be4b5dc0f8eb b/webm_parser/fuzzing/corpus/328b7c92996e480d1f11efe847c3be4b5dc0f8eb Binary files differnew file mode 100644 index 0000000..b229bcb --- /dev/null +++ b/webm_parser/fuzzing/corpus/328b7c92996e480d1f11efe847c3be4b5dc0f8eb diff --git a/webm_parser/fuzzing/corpus/32e58bb3d00863115f33707e0c8af722036676fd b/webm_parser/fuzzing/corpus/32e58bb3d00863115f33707e0c8af722036676fd Binary files differnew file mode 100644 index 0000000..caf3c09 --- /dev/null +++ b/webm_parser/fuzzing/corpus/32e58bb3d00863115f33707e0c8af722036676fd diff --git a/webm_parser/fuzzing/corpus/32fc8547b6145c502d98e5e0c296ebd05badfdb9 b/webm_parser/fuzzing/corpus/32fc8547b6145c502d98e5e0c296ebd05badfdb9 new file mode 100644 index 0000000..67c94c8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/32fc8547b6145c502d98e5e0c296ebd05badfdb9 @@ -0,0 +1 @@ +S€g˜T®k“®‘m€Žb@„P1b@€P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/334cddba889265a1263feb77a2afdc454ca54f5e b/webm_parser/fuzzing/corpus/334cddba889265a1263feb77a2afdc454ca54f5e new file mode 100644 index 0000000..e37d039 --- /dev/null +++ b/webm_parser/fuzzing/corpus/334cddba889265a1263feb77a2afdc454ca54f5e @@ -0,0 +1 @@ +S€gŒI©f‡*×±ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/33c9f9990890d2baf0c30d74b34b486082782413 b/webm_parser/fuzzing/corpus/33c9f9990890d2baf0c30d74b34b486082782413 Binary files differnew file mode 100644 index 0000000..9a1abea --- /dev/null +++ b/webm_parser/fuzzing/corpus/33c9f9990890d2baf0c30d74b34b486082782413 diff --git a/webm_parser/fuzzing/corpus/341f3e8689e57eeab4234fcdd6d2b8f800b9ab34 b/webm_parser/fuzzing/corpus/341f3e8689e57eeab4234fcdd6d2b8f800b9ab34 new file mode 100644 index 0000000..090bf59 --- /dev/null +++ b/webm_parser/fuzzing/corpus/341f3e8689e57eeab4234fcdd6d2b8f800b9ab34 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠè€Ìè€Ì
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3457e356eac79bf1c30b59940532d360dba2c1a9 b/webm_parser/fuzzing/corpus/3457e356eac79bf1c30b59940532d360dba2c1a9 new file mode 100644 index 0000000..3f813fb --- /dev/null +++ b/webm_parser/fuzzing/corpus/3457e356eac79bf1c30b59940532d360dba2c1a9 @@ -0,0 +1 @@ +S€gC¶uŠ ˆu¡…¦ƒîÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/351185a29bdbc7bf0d584479001fb47c32c61e5b b/webm_parser/fuzzing/corpus/351185a29bdbc7bf0d584479001fb47c32c61e5b new file mode 100644 index 0000000..6033e1c --- /dev/null +++ b/webm_parser/fuzzing/corpus/351185a29bdbc7bf0d584479001fb47c32c61e5b @@ -0,0 +1 @@ +!EߣˆB‚@webmS€g£M›t€I©f€C¶u€T®k€S»k€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/35ae0d43c6bfc5f4b45f16877157832fddafce77 b/webm_parser/fuzzing/corpus/35ae0d43c6bfc5f4b45f16877157832fddafce77 Binary files differnew file mode 100644 index 0000000..7e7e562 --- /dev/null +++ b/webm_parser/fuzzing/corpus/35ae0d43c6bfc5f4b45f16877157832fddafce77 diff --git a/webm_parser/fuzzing/corpus/35b2f81c573b15304cb9b13f00008b460da78942 b/webm_parser/fuzzing/corpus/35b2f81c573b15304cb9b13f00008b460da78942 new file mode 100644 index 0000000..85f4be2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/35b2f81c573b15304cb9b13f00008b460da78942 @@ -0,0 +1 @@ +S€gˆC¶uƒ«ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/35d9256a4d7ef3ee54384616178af3a8092d0981 b/webm_parser/fuzzing/corpus/35d9256a4d7ef3ee54384616178af3a8092d0981 new file mode 100644 index 0000000..42f1817 --- /dev/null +++ b/webm_parser/fuzzing/corpus/35d9256a4d7ef3ee54384616178af3a8092d0981 @@ -0,0 +1 @@ +S€gˆTÃgƒss€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/36352efd680a747f0f2c93d760559d69399a4102 b/webm_parser/fuzzing/corpus/36352efd680a747f0f2c93d760559d69399a4102 new file mode 100644 index 0000000..d438e9c --- /dev/null +++ b/webm_parser/fuzzing/corpus/36352efd680a747f0f2c93d760559d69399a4102 @@ -0,0 +1 @@ +S€gŠC¶u… ƒ›ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3685ffbcfe28b166ced0a783b012a446f9cfea46 b/webm_parser/fuzzing/corpus/3685ffbcfe28b166ced0a783b012a446f9cfea46 new file mode 100644 index 0000000..fe6660b --- /dev/null +++ b/webm_parser/fuzzing/corpus/3685ffbcfe28b166ced0a783b012a446f9cfea46 @@ -0,0 +1 @@ +S€gŒS»k‡»…·ƒ÷ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/36def8b2661197d594756f116a750822ae1d65d5 b/webm_parser/fuzzing/corpus/36def8b2661197d594756f116a750822ae1d65d5 new file mode 100644 index 0000000..50d822a --- /dev/null +++ b/webm_parser/fuzzing/corpus/36def8b2661197d594756f116a750822ae1d65d5 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P3
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/371950308853fb3b1b3a940c0598bfdfeb1d0f9c b/webm_parser/fuzzing/corpus/371950308853fb3b1b3a940c0598bfdfeb1d0f9c new file mode 100644 index 0000000..d12faf8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/371950308853fb3b1b3a940c0598bfdfeb1d0f9c @@ -0,0 +1 @@ +S€g‹C¶u† „u¢ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3765e9174971dbac9cfeb7e485ca61dc4941f7a9 b/webm_parser/fuzzing/corpus/3765e9174971dbac9cfeb7e485ca61dc4941f7a9 new file mode 100644 index 0000000..4c306d9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3765e9174971dbac9cfeb7e485ca61dc4941f7a9 @@ -0,0 +1 @@ +S€gžT®k™®—m€”b@‘P5ŽGç‹Gèˆ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/37cae036ce7ff5d0671f32d3757579f248609c9c b/webm_parser/fuzzing/corpus/37cae036ce7ff5d0671f32d3757579f248609c9c new file mode 100644 index 0000000..9183f01 --- /dev/null +++ b/webm_parser/fuzzing/corpus/37cae036ce7ff5d0671f32d3757579f248609c9c @@ -0,0 +1 @@ +S€g”T®k®m€Šb*‡P0„Gâ!S€gŠT®k…®ƒ¹ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/38460ea06ee847ff3d5733415c299b44b0866e58 b/webm_parser/fuzzing/corpus/38460ea06ee847ff3d5733415c299b44b0866e58 new file mode 100644 index 0000000..6456fa5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/38460ea06ee847ff3d5733415c299b44b0866e58 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶žsÄVTA‘@’@€@…@B€@…@C¶@sĶ@€ÿÿÿ¶@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3865de2fec3f353513d86b28e43bf936d4707f97 b/webm_parser/fuzzing/corpus/3865de2fec3f353513d86b28e43bf936d4707f97 new file mode 100644 index 0000000..f2150f6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3865de2fec3f353513d86b28e43bf936d4707f97 @@ -0,0 +1 @@ +€gS€g½C§p¸E¹µ¶³sÄVTA‘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3867b18060c307e0d04e0098f195d699e4b3294f b/webm_parser/fuzzing/corpus/3867b18060c307e0d04e0098f195d699e4b3294f new file mode 100644 index 0000000..f0cfd0a --- /dev/null +++ b/webm_parser/fuzzing/corpus/3867b18060c307e0d04e0098f195d699e4b3294f @@ -0,0 +1 @@ +S€g»TÃg¶ss³gțȄ#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ€D‡dgÈ€g
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/391920720c3609b7c1f7b51162ce3ec99329a0b4 b/webm_parser/fuzzing/corpus/391920720c3609b7c1f7b51162ce3ec99329a0b4 Binary files differnew file mode 100644 index 0000000..fb057c8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/391920720c3609b7c1f7b51162ce3ec99329a0b4 diff --git a/webm_parser/fuzzing/corpus/395027454c7d5babff9544414c04a39d712eed1c b/webm_parser/fuzzing/corpus/395027454c7d5babff9544414c04a39d712eed1c new file mode 100644 index 0000000..e34ef6f --- /dev/null +++ b/webm_parser/fuzzing/corpus/395027454c7d5babff9544414c04a39d712eed1c @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶ÿ¶€¶€¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€D¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€II diff --git a/webm_parser/fuzzing/corpus/3a258c9ed48e634ed8d495f07caf21dbc6978205 b/webm_parser/fuzzing/corpus/3a258c9ed48e634ed8d495f07caf21dbc6978205 Binary files differnew file mode 100644 index 0000000..a14b82b --- /dev/null +++ b/webm_parser/fuzzing/corpus/3a258c9ed48e634ed8d495f07caf21dbc6978205 diff --git a/webm_parser/fuzzing/corpus/3a397485197e6b99c77158132eaf211389615fe5 b/webm_parser/fuzzing/corpus/3a397485197e6b99c77158132eaf211389615fe5 new file mode 100644 index 0000000..f8697b8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3a397485197e6b99c77158132eaf211389615fe5 @@ -0,0 +1 @@ +S€gŠT®k…®ƒˆÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3a66b34706b686fe0fa8ca8f10c829758b2ebb07 b/webm_parser/fuzzing/corpus/3a66b34706b686fe0fa8ca8f10c829758b2ebb07 new file mode 100644 index 0000000..5062337 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3a66b34706b686fe0fa8ca8f10c829758b2ebb07 @@ -0,0 +1 @@ +S€g˜T®k“®‘m€Žb@„P1b€P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3ba666ad891fa8009dfc961aa3c215bbfd2d81d2 b/webm_parser/fuzzing/corpus/3ba666ad891fa8009dfc961aa3c215bbfd2d81d2 Binary files differnew file mode 100644 index 0000000..227e447 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3ba666ad891fa8009dfc961aa3c215bbfd2d81d2 diff --git a/webm_parser/fuzzing/corpus/3ba9769c7c23cc3c80542ad123a371cf4f69e858 b/webm_parser/fuzzing/corpus/3ba9769c7c23cc3c80542ad123a371cf4f69e858 Binary files differnew file mode 100644 index 0000000..d6bae74 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3ba9769c7c23cc3c80542ad123a371cf4f69e858 diff --git a/webm_parser/fuzzing/corpus/3beb4432302433e303a9ccaf34637dd3a910028e b/webm_parser/fuzzing/corpus/3beb4432302433e303a9ccaf34637dd3a910028e Binary files differnew file mode 100644 index 0000000..63ae212 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3beb4432302433e303a9ccaf34637dd3a910028e diff --git a/webm_parser/fuzzing/corpus/3c6cdebc450d20a58150a9c71335b45a6a5ee0e5 b/webm_parser/fuzzing/corpus/3c6cdebc450d20a58150a9c71335b45a6a5ee0e5 new file mode 100644 index 0000000..b92a4ed --- /dev/null +++ b/webm_parser/fuzzing/corpus/3c6cdebc450d20a58150a9c71335b45a6a5ee0e5 @@ -0,0 +1 @@ +S€gS¸
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3c867362eb6a20f573a890db87749fce3c719d14 b/webm_parser/fuzzing/corpus/3c867362eb6a20f573a890db87749fce3c719d14 Binary files differnew file mode 100644 index 0000000..8175642 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3c867362eb6a20f573a890db87749fce3c719d14 diff --git a/webm_parser/fuzzing/corpus/3cd69ddcd34bd874e3abade696f95c949c17198d b/webm_parser/fuzzing/corpus/3cd69ddcd34bd874e3abade696f95c949c17198d new file mode 100644 index 0000000..298c393 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3cd69ddcd34bd874e3abade696f95c949c17198d @@ -0,0 +1 @@ +S€gŒM›t‡M»„S«!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3de495ebac4b80064b79844f3a9453270caec251 b/webm_parser/fuzzing/corpus/3de495ebac4b80064b79844f3a9453270caec251 Binary files differnew file mode 100644 index 0000000..c4fb5a4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3de495ebac4b80064b79844f3a9453270caec251 diff --git a/webm_parser/fuzzing/corpus/3e04542bcc462ded0025b53371f709d8fcd25c21 b/webm_parser/fuzzing/corpus/3e04542bcc462ded0025b53371f709d8fcd25c21 Binary files differnew file mode 100644 index 0000000..8089b46 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3e04542bcc462ded0025b53371f709d8fcd25c21 diff --git a/webm_parser/fuzzing/corpus/3e94196e55a9214083bda470772d71aedc17eb5f b/webm_parser/fuzzing/corpus/3e94196e55a9214083bda470772d71aedc17eb5f Binary files differnew file mode 100644 index 0000000..18f0b8f --- /dev/null +++ b/webm_parser/fuzzing/corpus/3e94196e55a9214083bda470772d71aedc17eb5f diff --git a/webm_parser/fuzzing/corpus/3ec4ac00bb967a335b5caae0ae51488f2fda6583 b/webm_parser/fuzzing/corpus/3ec4ac00bb967a335b5caae0ae51488f2fda6583 Binary files differnew file mode 100644 index 0000000..b8043e8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3ec4ac00bb967a335b5caae0ae51488f2fda6583 diff --git a/webm_parser/fuzzing/corpus/3eec38c0ac3a96856d3210a0357857e70b07a9cb b/webm_parser/fuzzing/corpus/3eec38c0ac3a96856d3210a0357857e70b07a9cb new file mode 100644 index 0000000..25f0273 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3eec38c0ac3a96856d3210a0357857e70b07a9cb @@ -0,0 +1 @@ +S€g™TÃg‰ss†cÀ€cÀ€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3ef82ba46238fa46b89148a230d23e741ba5f1da b/webm_parser/fuzzing/corpus/3ef82ba46238fa46b89148a230d23e741ba5f1da new file mode 100644 index 0000000..134a456 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3ef82ba46238fa46b89148a230d23e741ba5f1da @@ -0,0 +1 @@ +S€g˜T®k“®‘m€b@„P1b@€b@€P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3fbd43f81888ca60e5bfe49b47e23cb115d5cd7d b/webm_parser/fuzzing/corpus/3fbd43f81888ca60e5bfe49b47e23cb115d5cd7d new file mode 100644 index 0000000..75b0d29 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3fbd43f81888ca60e5bfe49b47e23cb115d5cd7d @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶¬sÄV…@B€@…@C¶@sĶ@sä¶@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3fe56e5b06653e8a37c99da190ff8d44d14bfc06 b/webm_parser/fuzzing/corpus/3fe56e5b06653e8a37c99da190ff8d44d14bfc06 new file mode 100644 index 0000000..7a3c719 --- /dev/null +++ b/webm_parser/fuzzing/corpus/3fe56e5b06653e8a37c99da190ff8d44d14bfc06 @@ -0,0 +1 @@ +S€G™T®k”®’m€bŒP‰S€g“C¶uŽ ŒŽŠèƒÌá€èGƒÌâ€Gç€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/3febffec3be8b1f4bcb76a01c1d713600d8e4adb b/webm_parser/fuzzing/corpus/3febffec3be8b1f4bcb76a01c1d713600d8e4adb new file mode 100644 index 0000000..5f0a13d --- /dev/null +++ b/webm_parser/fuzzing/corpus/3febffec3be8b1f4bcb76a01c1d713600d8e4adb @@ -0,0 +1 @@ +S€gŠC§p…E¹‚¶€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/43380e5a9dd29ca847e9b5bfba12a0a95d94d0da b/webm_parser/fuzzing/corpus/43380e5a9dd29ca847e9b5bfba12a0a95d94d0da Binary files differnew file mode 100644 index 0000000..1c630fc --- /dev/null +++ b/webm_parser/fuzzing/corpus/43380e5a9dd29ca847e9b5bfba12a0a95d94d0da diff --git a/webm_parser/fuzzing/corpus/435d3ff6ddc43ac582e740ead969397fd2bfc7f5 b/webm_parser/fuzzing/corpus/435d3ff6ddc43ac582e740ead969397fd2bfc7f5 Binary files differnew file mode 100644 index 0000000..8d8a6f4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/435d3ff6ddc43ac582e740ead969397fd2bfc7f5 diff --git a/webm_parser/fuzzing/corpus/448120a37132488f48ab6c46a146c995948958cc b/webm_parser/fuzzing/corpus/448120a37132488f48ab6c46a146c995948958cc new file mode 100644 index 0000000..ff3b629 --- /dev/null +++ b/webm_parser/fuzzing/corpus/448120a37132488f48ab6c46a146c995948958cc @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P1S€g”T®k®m€Šb@€Ð5„Gáÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/46db501d3e9a8808118333740a22bf9c808af239 b/webm_parser/fuzzing/corpus/46db501d3e9a8808118333740a22bf9c808af239 Binary files differnew file mode 100644 index 0000000..a6c8ea7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/46db501d3e9a8808118333740a22bf9c808af239 diff --git a/webm_parser/fuzzing/corpus/47a2ca2b47aa1993cb655b77a0273ff139dd2280 b/webm_parser/fuzzing/corpus/47a2ca2b47aa1993cb655b77a0273ff139dd2280 Binary files differnew file mode 100644 index 0000000..21fea1b --- /dev/null +++ b/webm_parser/fuzzing/corpus/47a2ca2b47aa1993cb655b77a0273ff139dd2280 diff --git a/webm_parser/fuzzing/corpus/490576c8f6c235e1ed921c4932bac028070c806a b/webm_parser/fuzzing/corpus/490576c8f6c235e1ed921c4932bac028070c806a new file mode 100644 index 0000000..c83a83e --- /dev/null +++ b/webm_parser/fuzzing/corpus/490576c8f6c235e1ed921c4932bac028070c806a @@ -0,0 +1 @@ +S€g‘C¶uŒ Š¡‡¦…îƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4930b86bf0dfb02836b0319d21aa4a63a8dd894d b/webm_parser/fuzzing/corpus/4930b86bf0dfb02836b0319d21aa4a63a8dd894d new file mode 100644 index 0000000..3bc24db --- /dev/null +++ b/webm_parser/fuzzing/corpus/4930b86bf0dfb02836b0319d21aa4a63a8dd894d @@ -0,0 +1 @@ +S€gŠC¶u… ƒûÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/49667ff0b09901223117e74d2506bc37a5ae1580 b/webm_parser/fuzzing/corpus/49667ff0b09901223117e74d2506bc37a5ae1580 new file mode 100644 index 0000000..a7c53d4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/49667ff0b09901223117e74d2506bc37a5ae1580 @@ -0,0 +1 @@ +S€gŠT®k…®ƒ×
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/49906adf7313330e29be20d51fb36e9bdf1939c3 b/webm_parser/fuzzing/corpus/49906adf7313330e29be20d51fb36e9bdf1939c3 Binary files differnew file mode 100644 index 0000000..713aeb8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/49906adf7313330e29be20d51fb36e9bdf1939c3 diff --git a/webm_parser/fuzzing/corpus/49c0e3eed81c1827e755c70c9c6d9606e7f54b22 b/webm_parser/fuzzing/corpus/49c0e3eed81c1827e755c70c9c6d9606e7f54b22 Binary files differnew file mode 100644 index 0000000..cc061ed --- /dev/null +++ b/webm_parser/fuzzing/corpus/49c0e3eed81c1827e755c70c9c6d9606e7f54b22 diff --git a/webm_parser/fuzzing/corpus/4a3e77f5bc061fc8121ef7ebd2b8e3ebb0bdd609 b/webm_parser/fuzzing/corpus/4a3e77f5bc061fc8121ef7ebd2b8e3ebb0bdd609 new file mode 100644 index 0000000..336e6fb --- /dev/null +++ b/webm_parser/fuzzing/corpus/4a3e77f5bc061fc8121ef7ebd2b8e3ebb0bdd609 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶³sÄVTA‘@’@€@…@B€€€€j€€€€€€€€€€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4a454e01ef09b175f3864b654cfc1104a850e871 b/webm_parser/fuzzing/corpus/4a454e01ef09b175f3864b654cfc1104a850e871 Binary files differnew file mode 100644 index 0000000..f57bbfd --- /dev/null +++ b/webm_parser/fuzzing/corpus/4a454e01ef09b175f3864b654cfc1104a850e871 diff --git a/webm_parser/fuzzing/corpus/4acd9df9a7ee34e48c6c80777159b089f417a69a b/webm_parser/fuzzing/corpus/4acd9df9a7ee34e48c6c80777159b089f417a69a Binary files differnew file mode 100644 index 0000000..1df1e83 --- /dev/null +++ b/webm_parser/fuzzing/corpus/4acd9df9a7ee34e48c6c80777159b089f417a69a diff --git a/webm_parser/fuzzing/corpus/4af5564129d7b4c9c4736a3e511926c29348a4a4 b/webm_parser/fuzzing/corpus/4af5564129d7b4c9c4736a3e511926c29348a4a4 new file mode 100644 index 0000000..93af2db --- /dev/null +++ b/webm_parser/fuzzing/corpus/4af5564129d7b4c9c4736a3e511926c29348a4a4 @@ -0,0 +1 @@ +S€g™T®k”®’m€b@ŒP5‰Gá€â€Gç€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4b3957bcbe5975399e34485cf0e5229169ab67b9 b/webm_parser/fuzzing/corpus/4b3957bcbe5975399e34485cf0e5229169ab67b9 new file mode 100644 index 0000000..974197f --- /dev/null +++ b/webm_parser/fuzzing/corpus/4b3957bcbe5975399e34485cf0e5229169ab67b9 @@ -0,0 +1 @@ +S€gTÃgŠss‡cÀ„cÊ!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4c9e40aa8a439a500f073cf5a1e060a16ad12a6f b/webm_parser/fuzzing/corpus/4c9e40aa8a439a500f073cf5a1e060a16ad12a6f Binary files differnew file mode 100644 index 0000000..2d31d44 --- /dev/null +++ b/webm_parser/fuzzing/corpus/4c9e40aa8a439a500f073cf5a1e060a16ad12a6f diff --git a/webm_parser/fuzzing/corpus/4cb5ff0e26f5bd0bb1fca846b4843db69095f18b b/webm_parser/fuzzing/corpus/4cb5ff0e26f5bd0bb1fca846b4843db69095f18b new file mode 100644 index 0000000..db5373d --- /dev/null +++ b/webm_parser/fuzzing/corpus/4cb5ff0e26f5bd0bb1fca846b4843db69095f18b @@ -0,0 +1 @@ +S€gT®kˆ®†à„T³
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4ce80549344cf5b0432e39e7b5382407f0763e8c b/webm_parser/fuzzing/corpus/4ce80549344cf5b0432e39e7b5382407f0763e8c new file mode 100644 index 0000000..742ac60 --- /dev/null +++ b/webm_parser/fuzzing/corpus/4ce80549344cf5b0432e39e7b5382407f0763e8c @@ -0,0 +1 @@ +S€®ŽT”gk®m€Šb@‡P5„Gá
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4d10ae0032f04fa01148a54fae6838c44e4bb5bd b/webm_parser/fuzzing/corpus/4d10ae0032f04fa01148a54fae6838c44e4bb5bd new file mode 100644 index 0000000..97f34c6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/4d10ae0032f04fa01148a54fae6838c44e4bb5bd @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶sÄV…@B€@…@C¶@sĶ@sä¶@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4db2ded939a16fb1907dc2bad7acf67a64e38c0c b/webm_parser/fuzzing/corpus/4db2ded939a16fb1907dc2bad7acf67a64e38c0c new file mode 100644 index 0000000..36747bd --- /dev/null +++ b/webm_parser/fuzzing/corpus/4db2ded939a16fb1907dc2bad7acf67a64e38c0c @@ -0,0 +1 @@ +S€gˆC¶uƒç
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4dc3a9a9477fb452ba7a4026bbc01d4cdd3d4990 b/webm_parser/fuzzing/corpus/4dc3a9a9477fb452ba7a4026bbc01d4cdd3d4990 Binary files differnew file mode 100644 index 0000000..6422ec9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/4dc3a9a9477fb452ba7a4026bbc01d4cdd3d4990 diff --git a/webm_parser/fuzzing/corpus/4e1fb006a2268fa5fc70ca6b241bbc71faa6d7ff b/webm_parser/fuzzing/corpus/4e1fb006a2268fa5fc70ca6b241bbc71faa6d7ff new file mode 100644 index 0000000..1469145 --- /dev/null +++ b/webm_parser/fuzzing/corpus/4e1fb006a2268fa5fc70ca6b241bbc71faa6d7ff @@ -0,0 +1 @@ +S€g‚À
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4e871bbc5089f9550533641ddf6850f8722dfc36 b/webm_parser/fuzzing/corpus/4e871bbc5089f9550533641ddf6850f8722dfc36 Binary files differnew file mode 100644 index 0000000..5967f9e --- /dev/null +++ b/webm_parser/fuzzing/corpus/4e871bbc5089f9550533641ddf6850f8722dfc36 diff --git a/webm_parser/fuzzing/corpus/4eeb4507868732d3705d1ab10f6a31f70f579556 b/webm_parser/fuzzing/corpus/4eeb4507868732d3705d1ab10f6a31f70f579556 new file mode 100644 index 0000000..652484d --- /dev/null +++ b/webm_parser/fuzzing/corpus/4eeb4507868732d3705d1ab10f6a31f70f579556 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠèƒÌèƒÌS€gC¶uˆ †Ž„è‚΀
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/4f21f3584c99e97453ebe513ed44c86530fbebba b/webm_parser/fuzzing/corpus/4f21f3584c99e97453ebe513ed44c86530fbebba new file mode 100644 index 0000000..b37544d --- /dev/null +++ b/webm_parser/fuzzing/corpus/4f21f3584c99e97453ebe513ed44c86530fbebba @@ -0,0 +1 @@ +S€g¼C§p‹E¹ˆ¶†€„C|›C§p‹E¹ˆ¶†€„C|!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5103160695cd56d281faa45d9267013488cc7bde b/webm_parser/fuzzing/corpus/5103160695cd56d281faa45d9267013488cc7bde new file mode 100644 index 0000000..2a707f7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5103160695cd56d281faa45d9267013488cc7bde @@ -0,0 +1 @@ +S€gT®kŠ®ƒ×®ƒ×
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/521103054e395afd0f9612b47c71cfa0e62151b9 b/webm_parser/fuzzing/corpus/521103054e395afd0f9612b47c71cfa0e62151b9 Binary files differnew file mode 100644 index 0000000..5849044 --- /dev/null +++ b/webm_parser/fuzzing/corpus/521103054e395afd0f9612b47c71cfa0e62151b9 diff --git a/webm_parser/fuzzing/corpus/523511e33c8788ebb1b703d7c5ad12b5da03964e b/webm_parser/fuzzing/corpus/523511e33c8788ebb1b703d7c5ad12b5da03964e Binary files differnew file mode 100644 index 0000000..99e7799 --- /dev/null +++ b/webm_parser/fuzzing/corpus/523511e33c8788ebb1b703d7c5ad12b5da03964e diff --git a/webm_parser/fuzzing/corpus/525aedb7462311393cd01939e4734d01db48535b b/webm_parser/fuzzing/corpus/525aedb7462311393cd01939e4734d01db48535b new file mode 100644 index 0000000..a68bdd6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/525aedb7462311393cd01939e4734d01db48535b @@ -0,0 +1 @@ +V»UT³ÿM
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/53809012a8d5cfee839c3d0961b7fdc9468329a7 b/webm_parser/fuzzing/corpus/53809012a8d5cfee839c3d0961b7fdc9468329a7 new file mode 100644 index 0000000..50eef72 --- /dev/null +++ b/webm_parser/fuzzing/corpus/53809012a8d5cfee839c3d0961b7fdc9468329a7 @@ -0,0 +1 @@ +S€g»TÃg¶ss³È©È„#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ€D‡dgÈ€g
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/53c01d820d125e57b28410ca5b3a9c72c18d4102 b/webm_parser/fuzzing/corpus/53c01d820d125e57b28410ca5b3a9c72c18d4102 new file mode 100644 index 0000000..8f78c01 --- /dev/null +++ b/webm_parser/fuzzing/corpus/53c01d820d125e57b28410ca5b3a9c72c18d4102 @@ -0,0 +1 @@ +S€g‘C¶uŒ Šûˆþܺ˜vT2
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/53c27071f53df6b8d2e5c419edf95b7792b278e7 b/webm_parser/fuzzing/corpus/53c27071f53df6b8d2e5c419edf95b7792b278e7 new file mode 100644 index 0000000..87d69ed --- /dev/null +++ b/webm_parser/fuzzing/corpus/53c27071f53df6b8d2e5c419edf95b7792b278e7 @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶:€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶„¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€wÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶¶€¶ÿ¶€m¶ÿ¶€¶ÿ¶¶®®Ý€¶ÿ¶€¶ÿ¶€¶€µ¶€¶Œÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€å¶€¶€¶ÿ¶€Eߣ¶€¶À¶ÿ¶€m¶ÿ¶€¶ÿ¶¶®®Ý€¶ÿ¶€¶ÿ¶€¶€µ¶€¶Œÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/53d5307f2e7852b183839fe93c20647004d151ec b/webm_parser/fuzzing/corpus/53d5307f2e7852b183839fe93c20647004d151ec Binary files differnew file mode 100644 index 0000000..acecedb --- /dev/null +++ b/webm_parser/fuzzing/corpus/53d5307f2e7852b183839fe93c20647004d151ec diff --git a/webm_parser/fuzzing/corpus/5466c59c9c0eff34800e19cc92faf07469865ea9 b/webm_parser/fuzzing/corpus/5466c59c9c0eff34800e19cc92faf07469865ea9 new file mode 100644 index 0000000..2b86b64 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5466c59c9c0eff34800e19cc92faf07469865ea9 @@ -0,0 +1 @@ +VT¢€€ˆƒ€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/54bf8d6f1f846243b6f4de81605779b0acb78406 b/webm_parser/fuzzing/corpus/54bf8d6f1f846243b6f4de81605779b0acb78406 new file mode 100644 index 0000000..9b4a32e --- /dev/null +++ b/webm_parser/fuzzing/corpus/54bf8d6f1f846243b6f4de81605779b0acb78406 @@ -0,0 +1 @@ +€€€€€€€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/54fd1711209fb1c0781092374132c66e79e2241b b/webm_parser/fuzzing/corpus/54fd1711209fb1c0781092374132c66e79e2241b new file mode 100644 index 0000000..7937c68 --- /dev/null +++ b/webm_parser/fuzzing/corpus/54fd1711209fb1c0781092374132c66e79e2241b @@ -0,0 +1 @@ +g
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5506797514c0b214f5faa9764a9e149cd1df9d28 b/webm_parser/fuzzing/corpus/5506797514c0b214f5faa9764a9e149cd1df9d28 new file mode 100644 index 0000000..bfc5004 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5506797514c0b214f5faa9764a9e149cd1df9d28 @@ -0,0 +1 @@ +S€gŒT®k‡®…׃
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5566c6d599866f5e523c54e6575d7b9e557a2539 b/webm_parser/fuzzing/corpus/5566c6d599866f5e523c54e6575d7b9e557a2539 Binary files differnew file mode 100644 index 0000000..8578677 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5566c6d599866f5e523c54e6575d7b9e557a2539 diff --git a/webm_parser/fuzzing/corpus/55c3d55780427eb00bde252348db1c2ede22526c b/webm_parser/fuzzing/corpus/55c3d55780427eb00bde252348db1c2ede22526c new file mode 100644 index 0000000..25c7cc7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/55c3d55780427eb00bde252348db1c2ede22526c @@ -0,0 +1 @@ +€ diff --git a/webm_parser/fuzzing/corpus/5600abaefe804c50a353bbfdd0f9e10ffc102f81 b/webm_parser/fuzzing/corpus/5600abaefe804c50a353bbfdd0f9e10ffc102f81 new file mode 100644 index 0000000..2b5e073 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5600abaefe804c50a353bbfdd0f9e10ffc102f81 @@ -0,0 +1 @@ +S¸S€g‘TkŒ®Šm€‡b@„P1S€g”T®k®m€Šb@€Ð5„Gáÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5611ddd40488973ee6c64c45ad57d6eefe6da100 b/webm_parser/fuzzing/corpus/5611ddd40488973ee6c64c45ad57d6eefe6da100 new file mode 100644 index 0000000..34d12c7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5611ddd40488973ee6c64c45ad57d6eefe6da100 @@ -0,0 +1 @@ +S€gŠI©f…*×±ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/56b0ddd4e44492b45ebb7e9438b0a5556c244bfb b/webm_parser/fuzzing/corpus/56b0ddd4e44492b45ebb7e9438b0a5556c244bfb new file mode 100644 index 0000000..2da35a1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/56b0ddd4e44492b45ebb7e9438b0a5556c244bfb @@ -0,0 +1 @@ +S€g‹S»k†»„·€·€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5729395b452092152b06fbb8976e9bf1fda06439 b/webm_parser/fuzzing/corpus/5729395b452092152b06fbb8976e9bf1fda06439 Binary files differnew file mode 100644 index 0000000..df3e05b --- /dev/null +++ b/webm_parser/fuzzing/corpus/5729395b452092152b06fbb8976e9bf1fda06439 diff --git a/webm_parser/fuzzing/corpus/57cce6fac7c0992eb338e0e538d3e0ee3fe7f1cd b/webm_parser/fuzzing/corpus/57cce6fac7c0992eb338e0e538d3e0ee3fe7f1cd new file mode 100644 index 0000000..ab69784 --- /dev/null +++ b/webm_parser/fuzzing/corpus/57cce6fac7c0992eb338e0e538d3e0ee3fe7f1cd @@ -0,0 +1 @@ +ñEߣˆB‚@webmS€g£*M'›t€I©f€C¶u€ÔSkk»€®€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5822672f068da89546838004ae59552e12e132f7 b/webm_parser/fuzzing/corpus/5822672f068da89546838004ae59552e12e132f7 Binary files differnew file mode 100644 index 0000000..b5b45fe --- /dev/null +++ b/webm_parser/fuzzing/corpus/5822672f068da89546838004ae59552e12e132f7 diff --git a/webm_parser/fuzzing/corpus/59ce13e9a22e88dac879841256ff66f02cd2f8cc b/webm_parser/fuzzing/corpus/59ce13e9a22e88dac879841256ff66f02cd2f8cc new file mode 100644 index 0000000..628eda0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/59ce13e9a22e88dac879841256ff66f02cd2f8cc @@ -0,0 +1 @@ +S€gC¶u‹ ‰u¡†¦„€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5a020527fc80b0de64e279fc44635687e014a9cf b/webm_parser/fuzzing/corpus/5a020527fc80b0de64e279fc44635687e014a9cf Binary files differnew file mode 100644 index 0000000..50c33bd --- /dev/null +++ b/webm_parser/fuzzing/corpus/5a020527fc80b0de64e279fc44635687e014a9cf diff --git a/webm_parser/fuzzing/corpus/5a645b30948850f8efb7bdd11944bdf3112c7bfe b/webm_parser/fuzzing/corpus/5a645b30948850f8efb7bdd11944bdf3112c7bfe new file mode 100644 index 0000000..2d85ba9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5a645b30948850f8efb7bdd11944bdf3112c7bfe @@ -0,0 +1 @@ +S€gTÃgŠss‡cÀ„cÅÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5b2f727461cde3c02ffdfb300adb0b4a09734911 b/webm_parser/fuzzing/corpus/5b2f727461cde3c02ffdfb300adb0b4a09734911 new file mode 100644 index 0000000..7416715 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5b2f727461cde3c02ffdfb300adb0b4a09734911 @@ -0,0 +1 @@ +S€gT®kŠ®ˆà†S¸ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5b5531d859ab5bbb034345bbaa2852e9d8a1e1ef b/webm_parser/fuzzing/corpus/5b5531d859ab5bbb034345bbaa2852e9d8a1e1ef new file mode 100644 index 0000000..5b800b8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5b5531d859ab5bbb034345bbaa2852e9d8a1e1ef @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5b980cb94e35769ec92a140ca00d34977e7da5ec b/webm_parser/fuzzing/corpus/5b980cb94e35769ec92a140ca00d34977e7da5ec Binary files differnew file mode 100644 index 0000000..180ef6b --- /dev/null +++ b/webm_parser/fuzzing/corpus/5b980cb94e35769ec92a140ca00d34977e7da5ec diff --git a/webm_parser/fuzzing/corpus/5bfec2fe795176646fa0babecf97c04045bd4814 b/webm_parser/fuzzing/corpus/5bfec2fe795176646fa0babecf97c04045bd4814 Binary files differnew file mode 100644 index 0000000..9e406d3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5bfec2fe795176646fa0babecf97c04045bd4814 diff --git a/webm_parser/fuzzing/corpus/5c28d94242e9a5ba7e7324e59e5dd12f3e31e51c b/webm_parser/fuzzing/corpus/5c28d94242e9a5ba7e7324e59e5dd12f3e31e51c Binary files differnew file mode 100644 index 0000000..68b4ebc --- /dev/null +++ b/webm_parser/fuzzing/corpus/5c28d94242e9a5ba7e7324e59e5dd12f3e31e51c diff --git a/webm_parser/fuzzing/corpus/5c67728ab04ba3dfe4b2636cf609f9617c4a5974 b/webm_parser/fuzzing/corpus/5c67728ab04ba3dfe4b2636cf609f9617c4a5974 new file mode 100644 index 0000000..f14a6f6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5c67728ab04ba3dfe4b2636cf609f9617c4a5974 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P0„Gâ!S€gŠT®k…®ƒ¹ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5cd36053e269c97f5359b0a075d80bf1e23b5f47 b/webm_parser/fuzzing/corpus/5cd36053e269c97f5359b0a075d80bf1e23b5f47 new file mode 100644 index 0000000..985bba4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5cd36053e269c97f5359b0a075d80bf1e23b5f47 @@ -0,0 +1 @@ +S€gŒC¶u‡ …u¡‚¦€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/5d6f99c2afbf68a1b15a17e07945c11db8579e5f b/webm_parser/fuzzing/corpus/5d6f99c2afbf68a1b15a17e07945c11db8579e5f Binary files differnew file mode 100644 index 0000000..85a0c93 --- /dev/null +++ b/webm_parser/fuzzing/corpus/5d6f99c2afbf68a1b15a17e07945c11db8579e5f diff --git a/webm_parser/fuzzing/corpus/5dd149b1b8509f456c16c4ac6dac3c7d6aa217ed b/webm_parser/fuzzing/corpus/5dd149b1b8509f456c16c4ac6dac3c7d6aa217ed Binary files differnew file mode 100644 index 0000000..102660d --- /dev/null +++ b/webm_parser/fuzzing/corpus/5dd149b1b8509f456c16c4ac6dac3c7d6aa217ed diff --git a/webm_parser/fuzzing/corpus/5e84011c6b092e9f0d6ba60f12fc0fae95a622ca b/webm_parser/fuzzing/corpus/5e84011c6b092e9f0d6ba60f12fc0fae95a622ca Binary files differnew file mode 100644 index 0000000..d4df4df --- /dev/null +++ b/webm_parser/fuzzing/corpus/5e84011c6b092e9f0d6ba60f12fc0fae95a622ca diff --git a/webm_parser/fuzzing/corpus/5f56766993de2b8c2db87116090af546e1add3b0 b/webm_parser/fuzzing/corpus/5f56766993de2b8c2db87116090af546e1add3b0 new file mode 100644 index 0000000..c90364d --- /dev/null +++ b/webm_parser/fuzzing/corpus/5f56766993de2b8c2db87116090af546e1add3b0 @@ -0,0 +1 @@ +S€gŽS»k‰»‡·…÷ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/60571c8be9bee3b13428613df66ff36cf54309ec b/webm_parser/fuzzing/corpus/60571c8be9bee3b13428613df66ff36cf54309ec new file mode 100644 index 0000000..bfd35dc --- /dev/null +++ b/webm_parser/fuzzing/corpus/60571c8be9bee3b13428613df66ff36cf54309ec @@ -0,0 +1 @@ +S€g”TÃgssŒcÀ‰hÊ€cÊ€cÅ€S€gTÃgŠss‡cÀ„cÊ!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/60ac245f262ee1ff177d351a4fe246060fb4538b b/webm_parser/fuzzing/corpus/60ac245f262ee1ff177d351a4fe246060fb4538b Binary files differnew file mode 100644 index 0000000..21219e9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/60ac245f262ee1ff177d351a4fe246060fb4538b diff --git a/webm_parser/fuzzing/corpus/6120aafb0db1715400e47d0a61fb9f6d8665a736 b/webm_parser/fuzzing/corpus/6120aafb0db1715400e47d0a61fb9f6d8665a736 new file mode 100644 index 0000000..2fc4e39 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6120aafb0db1715400e47d0a61fb9f6d8665a736 @@ -0,0 +1 @@ +S€gŠC¶u…çƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/61953a8f7a9f0613020d297a6d350abeeaf6e58c b/webm_parser/fuzzing/corpus/61953a8f7a9f0613020d297a6d350abeeaf6e58c new file mode 100644 index 0000000..478128c --- /dev/null +++ b/webm_parser/fuzzing/corpus/61953a8f7a9f0613020d297a6d350abeeaf6e58c @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠèƒÌèƒÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/61c0a7fef46c0c1b081b1c2b40835805e5989b54 b/webm_parser/fuzzing/corpus/61c0a7fef46c0c1b081b1c2b40835805e5989b54 new file mode 100644 index 0000000..b5ac3f6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/61c0a7fef46c0c1b081b1c2b40835805e5989b54 @@ -0,0 +1 @@ +ä
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/620d85870206e88db0bc3ee978c24c0bbc1821ba b/webm_parser/fuzzing/corpus/620d85870206e88db0bc3ee978c24c0bbc1821ba Binary files differnew file mode 100644 index 0000000..4749f9c --- /dev/null +++ b/webm_parser/fuzzing/corpus/620d85870206e88db0bc3ee978c24c0bbc1821ba diff --git a/webm_parser/fuzzing/corpus/621131d8e28a67a6b4fb03c611f4375873d69678 b/webm_parser/fuzzing/corpus/621131d8e28a67a6b4fb03c611f4375873d69678 new file mode 100644 index 0000000..d037403 --- /dev/null +++ b/webm_parser/fuzzing/corpus/621131d8e28a67a6b4fb03c611f4375873d69678 @@ -0,0 +1 @@ +S€gŽC¶u‰ ‡Ž…èƒÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/624cbd42e3ebf35a3a0ba55b3b4969b99054f84d b/webm_parser/fuzzing/corpus/624cbd42e3ebf35a3a0ba55b3b4969b99054f84d Binary files differnew file mode 100644 index 0000000..0b518b9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/624cbd42e3ebf35a3a0ba55b3b4969b99054f84d diff --git a/webm_parser/fuzzing/corpus/6290103200631075988aa3566d8d8922599f8b6d b/webm_parser/fuzzing/corpus/6290103200631075988aa3566d8d8922599f8b6d new file mode 100644 index 0000000..52f240d --- /dev/null +++ b/webm_parser/fuzzing/corpus/6290103200631075988aa3566d8d8922599f8b6d @@ -0,0 +1 @@ +€ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/62a329f65143f1571dc4a2fbb256800c8ce4d89f b/webm_parser/fuzzing/corpus/62a329f65143f1571dc4a2fbb256800c8ce4d89f Binary files differnew file mode 100644 index 0000000..eca1ba9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/62a329f65143f1571dc4a2fbb256800c8ce4d89f diff --git a/webm_parser/fuzzing/corpus/62f8a53cfb4dd625dcb31ee2ba258db3916a3bd0 b/webm_parser/fuzzing/corpus/62f8a53cfb4dd625dcb31ee2ba258db3916a3bd0 new file mode 100644 index 0000000..a3a36dc --- /dev/null +++ b/webm_parser/fuzzing/corpus/62f8a53cfb4dd625dcb31ee2ba258db3916a3bd0 @@ -0,0 +1 @@ +S€gTÃg˜ss•gÈ’E£€gÈ€D„€D‡€D…€gÈ€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6318dc574b3ad7291fee7e01bfeaa674eb0feb48 b/webm_parser/fuzzing/corpus/6318dc574b3ad7291fee7e01bfeaa674eb0feb48 new file mode 100644 index 0000000..baa7b64 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6318dc574b3ad7291fee7e01bfeaa674eb0feb48 @@ -0,0 +1 @@ +S€g˜T®k“®‘m€Žb@„P1b@„P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6457a079debb9b532255e3e6ddf276b29808b49d b/webm_parser/fuzzing/corpus/6457a079debb9b532255e3e6ddf276b29808b49d Binary files differnew file mode 100644 index 0000000..640d4a1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6457a079debb9b532255e3e6ddf276b29808b49d diff --git a/webm_parser/fuzzing/corpus/646061a4526506a0b3c62629f4f77b6072c8f9c3 b/webm_parser/fuzzing/corpus/646061a4526506a0b3c62629f4f77b6072c8f9c3 new file mode 100644 index 0000000..beb9e5b --- /dev/null +++ b/webm_parser/fuzzing/corpus/646061a4526506a0b3c62629f4f77b6072c8f9c3 @@ -0,0 +1 @@ +EߣžB†‡
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6538d0517865c8832ee9bec87609b7bd4facb025 b/webm_parser/fuzzing/corpus/6538d0517865c8832ee9bec87609b7bd4facb025 new file mode 100644 index 0000000..7230e70 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6538d0517865c8832ee9bec87609b7bd4facb025 @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ„€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶¶¶ÿÿ€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶Š¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI diff --git a/webm_parser/fuzzing/corpus/653de28e2f085334e30ec1c964540b432a3cfbc6 b/webm_parser/fuzzing/corpus/653de28e2f085334e30ec1c964540b432a3cfbc6 new file mode 100644 index 0000000..5748705 --- /dev/null +++ b/webm_parser/fuzzing/corpus/653de28e2f085334e30ec1c964540b432a3cfbc6 @@ -0,0 +1 @@ +S€gC§p‹E¹ˆ¶†€„C|!S€gŽC§p‰E¹†¶„VT
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/657c9c42bf3e1c79a6dcb9071bf123367dc37734 b/webm_parser/fuzzing/corpus/657c9c42bf3e1c79a6dcb9071bf123367dc37734 Binary files differnew file mode 100644 index 0000000..8638bce --- /dev/null +++ b/webm_parser/fuzzing/corpus/657c9c42bf3e1c79a6dcb9071bf123367dc37734 diff --git a/webm_parser/fuzzing/corpus/65ebba0df08a5484686905e99631a4ead845c08b b/webm_parser/fuzzing/corpus/65ebba0df08a5484686905e99631a4ead845c08b new file mode 100644 index 0000000..e04df80 --- /dev/null +++ b/webm_parser/fuzzing/corpus/65ebba0df08a5484686905e99631a4ead845c08b @@ -0,0 +1 @@ +S€gŽM›t‰M»†S¬ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/66dd27c22fe09842ac4c4f51fdef4bea500b6a70 b/webm_parser/fuzzing/corpus/66dd27c22fe09842ac4c4f51fdef4bea500b6a70 new file mode 100644 index 0000000..2e5c016 --- /dev/null +++ b/webm_parser/fuzzing/corpus/66dd27c22fe09842ac4c4f51fdef4bea500b6a70 @@ -0,0 +1 @@ +EߣÿB‚è
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/66e25df2bd43c840337a49e0cb5b386e9d290d80 b/webm_parser/fuzzing/corpus/66e25df2bd43c840337a49e0cb5b386e9d290d80 Binary files differnew file mode 100644 index 0000000..88bbf1f --- /dev/null +++ b/webm_parser/fuzzing/corpus/66e25df2bd43c840337a49e0cb5b386e9d290d80 diff --git a/webm_parser/fuzzing/corpus/6714df1ac1c54d4c30add212454e9513c6880767 b/webm_parser/fuzzing/corpus/6714df1ac1c54d4c30add212454e9513c6880767 new file mode 100644 index 0000000..20b7616 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6714df1ac1c54d4c30add212454e9513c6880767 @@ -0,0 +1 @@ +S€gŒT®k‡®…àƒ°
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/68a6f9c2f62e9f57667de9563b5d612ec2f2c829 b/webm_parser/fuzzing/corpus/68a6f9c2f62e9f57667de9563b5d612ec2f2c829 Binary files differnew file mode 100644 index 0000000..d8309ea --- /dev/null +++ b/webm_parser/fuzzing/corpus/68a6f9c2f62e9f57667de9563b5d612ec2f2c829 diff --git a/webm_parser/fuzzing/corpus/69089b76cc62fe00615702bdd1f339e259194d42 b/webm_parser/fuzzing/corpus/69089b76cc62fe00615702bdd1f339e259194d42 new file mode 100644 index 0000000..f925002 --- /dev/null +++ b/webm_parser/fuzzing/corpus/69089b76cc62fe00615702bdd1f339e259194d42 @@ -0,0 +1 @@ +S€gŽT®k‰®‡à…šƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/69d8622d24c7315f344c424297986170f7ef2243 b/webm_parser/fuzzing/corpus/69d8622d24c7315f344c424297986170f7ef2243 new file mode 100644 index 0000000..6182f76 --- /dev/null +++ b/webm_parser/fuzzing/corpus/69d8622d24c7315f344c424297986170f7ef2243 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠèƒÌèƒÌS€gC¶uˆ †Ž„è‚Ì€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6a700d958cb3a7565d5a6b74540486874d52b4c7 b/webm_parser/fuzzing/corpus/6a700d958cb3a7565d5a6b74540486874d52b4c7 new file mode 100644 index 0000000..3c4a4a4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6a700d958cb3a7565d5a6b74540486874d52b4c7 @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶²€ …@helloC|…lang0C~…area0C|…lang€…S|1C–T®k‘®m€Œb@‰P2€ÿ†G*lang2C~ƒGè…€area1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6a852150c5c2a12d325e78056687a8ea97b25fe7 b/webm_parser/fuzzing/corpus/6a852150c5c2a12d325e78056687a8ea97b25fe7 new file mode 100644 index 0000000..ca14558 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6a852150c5c2a12d325e78056687a8ea97b25fe7 @@ -0,0 +1 @@ +S€g‡T®k‚®€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6ac8c9f754d5ff3c0cd3f3f585de99dd73944bbe b/webm_parser/fuzzing/corpus/6ac8c9f754d5ff3c0cd3f3f585de99dd73944bbe Binary files differnew file mode 100644 index 0000000..a7d88f0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6ac8c9f754d5ff3c0cd3f3f585de99dd73944bbe diff --git a/webm_parser/fuzzing/corpus/6ace27a48f12990e2169cc099007aa5fa11962d2 b/webm_parser/fuzzing/corpus/6ace27a48f12990e2169cc099007aa5fa11962d2 new file mode 100644 index 0000000..f544930 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6ace27a48f12990e2169cc099007aa5fa11962d2 @@ -0,0 +1 @@ +S€g™T®k”®’m€b@ŒT2€P2€P3P5€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6b10397dc3bff2ccf57be4fe3ff9d8d1acb8c76e b/webm_parser/fuzzing/corpus/6b10397dc3bff2ccf57be4fe3ff9d8d1acb8c76e Binary files differnew file mode 100644 index 0000000..db9ed48 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6b10397dc3bff2ccf57be4fe3ff9d8d1acb8c76e diff --git a/webm_parser/fuzzing/corpus/6c3f1d96c06b853551f685dde821142928f55acf b/webm_parser/fuzzing/corpus/6c3f1d96c06b853551f685dde821142928f55acf Binary files differnew file mode 100644 index 0000000..91f4e81 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6c3f1d96c06b853551f685dde821142928f55acf diff --git a/webm_parser/fuzzing/corpus/6c8de4d4bbb5294154c3b140d43836757b9f08ac b/webm_parser/fuzzing/corpus/6c8de4d4bbb5294154c3b140d43836757b9f08ac new file mode 100644 index 0000000..8aefea2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6c8de4d4bbb5294154c3b140d43836757b9f08ac @@ -0,0 +1 @@ +S€g‰I©f„{©!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6cf122ee328af5cf6f18d5f9546b0fe761951b8d b/webm_parser/fuzzing/corpus/6cf122ee328af5cf6f18d5f9546b0fe761951b8d Binary files differnew file mode 100644 index 0000000..5634639 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6cf122ee328af5cf6f18d5f9546b0fe761951b8d diff --git a/webm_parser/fuzzing/corpus/6cfbbc25caa217e22fd7ca3e92d2051c5f661bdb b/webm_parser/fuzzing/corpus/6cfbbc25caa217e22fd7ca3e92d2051c5f661bdb new file mode 100644 index 0000000..bc69d35 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6cfbbc25caa217e22fd7ca3e92d2051c5f661bdb @@ -0,0 +1 @@ +S€gC¶uˆ †Ž„è‚Ì€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6d703bc900e74d1a576bebcccb63e3b2701ae86d b/webm_parser/fuzzing/corpus/6d703bc900e74d1a576bebcccb63e3b2701ae86d new file mode 100644 index 0000000..e0bdcf2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6d703bc900e74d1a576bebcccb63e3b2701ae86d @@ -0,0 +1 @@ +S€g˜T®k“®‘m€Žb@„P1b@€b@€P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6e1afa81dfcd6833705d84b45881f085c19b2211 b/webm_parser/fuzzing/corpus/6e1afa81dfcd6833705d84b45881f085c19b2211 new file mode 100644 index 0000000..670dd7c --- /dev/null +++ b/webm_parser/fuzzing/corpus/6e1afa81dfcd6833705d84b45881f085c19b2211 @@ -0,0 +1 @@ +S€gC¶uŠ ˆu¡…¦ƒ¥!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6e244ec3b81e0d2f8b5810854859e1b8140422d9 b/webm_parser/fuzzing/corpus/6e244ec3b81e0d2f8b5810854859e1b8140422d9 new file mode 100644 index 0000000..2e7c1a4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6e244ec3b81e0d2f8b5810854859e1b8140422d9 @@ -0,0 +1 @@ +S€gûû
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6e323b4c733df90d55c754e099fb5a13bd9701cb b/webm_parser/fuzzing/corpus/6e323b4c733df90d55c754e099fb5a13bd9701cb new file mode 100644 index 0000000..89e0e79 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6e323b4c733df90d55c754e099fb5a13bd9701cb @@ -0,0 +1 @@ +S€gT®kŠ®ˆà†T°ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6e36720f3f55dc0cd5d585c6770572964719b5b8 b/webm_parser/fuzzing/corpus/6e36720f3f55dc0cd5d585c6770572964719b5b8 Binary files differnew file mode 100644 index 0000000..376c75b --- /dev/null +++ b/webm_parser/fuzzing/corpus/6e36720f3f55dc0cd5d585c6770572964719b5b8 diff --git a/webm_parser/fuzzing/corpus/6e8384ab115ac92bb7787121cfb03582e2d72585 b/webm_parser/fuzzing/corpus/6e8384ab115ac92bb7787121cfb03582e2d72585 new file mode 100644 index 0000000..7f90100 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6e8384ab115ac92bb7787121cfb03582e2d72585 @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶ž|°…@hello€€€€€€€€€…@B€@…@C¶@
sÄ€€€€€€„€–€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6ebf8b74dca1657f5f7ffadeccd629984473dcb4 b/webm_parser/fuzzing/corpus/6ebf8b74dca1657f5f7ffadeccd629984473dcb4 Binary files differnew file mode 100644 index 0000000..0bf3c1b --- /dev/null +++ b/webm_parser/fuzzing/corpus/6ebf8b74dca1657f5f7ffadeccd629984473dcb4 diff --git a/webm_parser/fuzzing/corpus/6ec18dcca0d820fff04c03c3a1bce37dd8ada41d b/webm_parser/fuzzing/corpus/6ec18dcca0d820fff04c03c3a1bce37dd8ada41d new file mode 100644 index 0000000..c10fff0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6ec18dcca0d820fff04c03c3a1bce37dd8ada41d @@ -0,0 +1 @@ +S€gT®k‹®‰á‡xµ„@ÉÛ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6eceebb8f46545393d217ab0111ff7dd29bc99f7 b/webm_parser/fuzzing/corpus/6eceebb8f46545393d217ab0111ff7dd29bc99f7 new file mode 100644 index 0000000..27952aa --- /dev/null +++ b/webm_parser/fuzzing/corpus/6eceebb8f46545393d217ab0111ff7dd29bc99f7 @@ -0,0 +1 @@ +S€g–T®k‘®m€Œb@‰P5€†GçƒGè€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6ee071f654fd6b7b25725a8bdd880bb6836e8c81 b/webm_parser/fuzzing/corpus/6ee071f654fd6b7b25725a8bdd880bb6836e8c81 Binary files differnew file mode 100644 index 0000000..fc5bea2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/6ee071f654fd6b7b25725a8bdd880bb6836e8c81 diff --git a/webm_parser/fuzzing/corpus/6f1088b3d1f4cdfbc37d9b8cfc1ff4c3bde2f205 b/webm_parser/fuzzing/corpus/6f1088b3d1f4cdfbc37d9b8cfc1ff4c3bde2f205 new file mode 100644 index 0000000..9936eea --- /dev/null +++ b/webm_parser/fuzzing/corpus/6f1088b3d1f4cdfbc37d9b8cfc1ff4c3bde2f205 @@ -0,0 +1 @@ +EߣˆB‚@webmS€g©C¶u¤ç@«@£…¾Eß#
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6f79e36e880664c9b3b610140d2485fb6a6e5039 b/webm_parser/fuzzing/corpus/6f79e36e880664c9b3b610140d2485fb6a6e5039 new file mode 100644 index 0000000..e646c0d --- /dev/null +++ b/webm_parser/fuzzing/corpus/6f79e36e880664c9b3b610140d2485fb6a6e5039 @@ -0,0 +1 @@ +€g€g=C§p¸Ess¶³sÄVTA‘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/6fbea59066b2b97fa45259aa87c40c4310070019 b/webm_parser/fuzzing/corpus/6fbea59066b2b97fa45259aa87c40c4310070019 new file mode 100644 index 0000000..6b33dfd --- /dev/null +++ b/webm_parser/fuzzing/corpus/6fbea59066b2b97fa45259aa87c40c4310070019 @@ -0,0 +1 @@ +EߣˆB‚@webmS€g©C¶u¤ç@!«
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7056b9c294f0cff7b4ace611a19363b2fc096bb3 b/webm_parser/fuzzing/corpus/7056b9c294f0cff7b4ace611a19363b2fc096bb3 new file mode 100644 index 0000000..b0e999e --- /dev/null +++ b/webm_parser/fuzzing/corpus/7056b9c294f0cff7b4ace611a19363b2fc096bb3 @@ -0,0 +1 @@ +!EߣˆB‚@webmS€g£M'›t)€I©f€OC¶u€gT®k€S»k€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7073dca40b911fb22f738c99aff43a10ae6c4db5 b/webm_parser/fuzzing/corpus/7073dca40b911fb22f738c99aff43a10ae6c4db5 Binary files differnew file mode 100644 index 0000000..383c77a --- /dev/null +++ b/webm_parser/fuzzing/corpus/7073dca40b911fb22f738c99aff43a10ae6c4db5 diff --git a/webm_parser/fuzzing/corpus/7164d8ca3f4c3d621cc5eed7b59e3495e7b017a0 b/webm_parser/fuzzing/corpus/7164d8ca3f4c3d621cc5eed7b59e3495e7b017a0 Binary files differnew file mode 100644 index 0000000..7d8a5d1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7164d8ca3f4c3d621cc5eed7b59e3495e7b017a0 diff --git a/webm_parser/fuzzing/corpus/71e3e01b3cfcd4228402cbf19b5282c965f042df b/webm_parser/fuzzing/corpus/71e3e01b3cfcd4228402cbf19b5282c965f042df new file mode 100644 index 0000000..72cc1dc --- /dev/null +++ b/webm_parser/fuzzing/corpus/71e3e01b3cfcd4228402cbf19b5282c965f042df @@ -0,0 +1 @@ +ìÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/720d36ea7f9616b1a1d47fa5b746e8657b5f973f b/webm_parser/fuzzing/corpus/720d36ea7f9616b1a1d47fa5b746e8657b5f973f Binary files differnew file mode 100644 index 0000000..6de803d --- /dev/null +++ b/webm_parser/fuzzing/corpus/720d36ea7f9616b1a1d47fa5b746e8657b5f973f diff --git a/webm_parser/fuzzing/corpus/72ccbcbc53c7fa54247a81bd1d0e62dd0779494b b/webm_parser/fuzzing/corpus/72ccbcbc53c7fa54247a81bd1d0e62dd0779494b new file mode 100644 index 0000000..20c7fa6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/72ccbcbc53c7fa54247a81bd1d0e62dd0779494b @@ -0,0 +1 @@ +!EߣˆB‚@webmS€g£M'›t€I©f€C¶u€T®k€S»k€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/736c0460f94c089c9e270857a4222a21ffd9e13f b/webm_parser/fuzzing/corpus/736c0460f94c089c9e270857a4222a21ffd9e13f Binary files differnew file mode 100644 index 0000000..14a490e --- /dev/null +++ b/webm_parser/fuzzing/corpus/736c0460f94c089c9e270857a4222a21ffd9e13f diff --git a/webm_parser/fuzzing/corpus/745e32f46833c075c8e68552e0fbbcdd5ec52641 b/webm_parser/fuzzing/corpus/745e32f46833c075c8e68552e0fbbcdd5ec52641 Binary files differnew file mode 100644 index 0000000..518d182 --- /dev/null +++ b/webm_parser/fuzzing/corpus/745e32f46833c075c8e68552e0fbbcdd5ec52641 diff --git a/webm_parser/fuzzing/corpus/74aa7256477af2c0e2511df16376dee323f3ccb6 b/webm_parser/fuzzing/corpus/74aa7256477af2c0e2511df16376dee323f3ccb6 Binary files differnew file mode 100644 index 0000000..46cfdb1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/74aa7256477af2c0e2511df16376dee323f3ccb6 diff --git a/webm_parser/fuzzing/corpus/7528086eea404dccc3ee5b214befbc2a95676feb b/webm_parser/fuzzing/corpus/7528086eea404dccc3ee5b214befbc2a95676feb new file mode 100644 index 0000000..40b7db2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7528086eea404dccc3ee5b214befbc2a95676feb @@ -0,0 +1 @@ +S€g‘T®kŒ®Šàˆ#ƒã„ÉÛ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7528f9f08f80c8e85951aa1db2dc616e992c1a62 b/webm_parser/fuzzing/corpus/7528f9f08f80c8e85951aa1db2dc616e992c1a62 Binary files differnew file mode 100644 index 0000000..87b29be --- /dev/null +++ b/webm_parser/fuzzing/corpus/7528f9f08f80c8e85951aa1db2dc616e992c1a62 diff --git a/webm_parser/fuzzing/corpus/75af94f9695c375e5eb620849f0213ad41e8f205 b/webm_parser/fuzzing/corpus/75af94f9695c375e5eb620849f0213ad41e8f205 Binary files differnew file mode 100644 index 0000000..dcf5dc6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/75af94f9695c375e5eb620849f0213ad41e8f205 diff --git a/webm_parser/fuzzing/corpus/75e1ff2e016b7b80ecb7a6a1f4c3238a2a2ed130 b/webm_parser/fuzzing/corpus/75e1ff2e016b7b80ecb7a6a1f4c3238a2a2ed130 Binary files differnew file mode 100644 index 0000000..9dcbbb5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/75e1ff2e016b7b80ecb7a6a1f4c3238a2a2ed130 diff --git a/webm_parser/fuzzing/corpus/76129c3bdedafb3bda93f53266bd17bffdc30682 b/webm_parser/fuzzing/corpus/76129c3bdedafb3bda93f53266bd17bffdc30682 Binary files differnew file mode 100644 index 0000000..bb8e055 --- /dev/null +++ b/webm_parser/fuzzing/corpus/76129c3bdedafb3bda93f53266bd17bffdc30682 diff --git a/webm_parser/fuzzing/corpus/7670db89e0c3fe492c91458de219c3be34a3dcb7 b/webm_parser/fuzzing/corpus/7670db89e0c3fe492c91458de219c3be34a3dcb7 Binary files differnew file mode 100644 index 0000000..c57ba43 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7670db89e0c3fe492c91458de219c3be34a3dcb7 diff --git a/webm_parser/fuzzing/corpus/7673d7fdf630637c6ae2b96694a047044c1a9cb0 b/webm_parser/fuzzing/corpus/7673d7fdf630637c6ae2b96694a047044c1a9cb0 Binary files differnew file mode 100644 index 0000000..b0955cb --- /dev/null +++ b/webm_parser/fuzzing/corpus/7673d7fdf630637c6ae2b96694a047044c1a9cb0 diff --git a/webm_parser/fuzzing/corpus/76903905e654ddc59acf57380bf3dc1ea136bad9 b/webm_parser/fuzzing/corpus/76903905e654ddc59acf57380bf3dc1ea136bad9 new file mode 100644 index 0000000..f652c35 --- /dev/null +++ b/webm_parser/fuzzing/corpus/76903905e654ddc59acf57380bf3dc1ea136bad9 @@ -0,0 +1 @@ +S€gT®kˆ®†à„T²ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/76d36e3cf3d31e0f76af08bd7ba5571f679057a1 b/webm_parser/fuzzing/corpus/76d36e3cf3d31e0f76af08bd7ba5571f679057a1 new file mode 100644 index 0000000..3f6abf8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/76d36e3cf3d31e0f76af08bd7ba5571f679057a1 @@ -0,0 +1 @@ +S€gŠC¶u… ƒû
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/775ce646f7dbf50199b8e8df85c9441b8a0a5447 b/webm_parser/fuzzing/corpus/775ce646f7dbf50199b8e8df85c9441b8a0a5447 Binary files differnew file mode 100644 index 0000000..3d451af --- /dev/null +++ b/webm_parser/fuzzing/corpus/775ce646f7dbf50199b8e8df85c9441b8a0a5447 diff --git a/webm_parser/fuzzing/corpus/7770a6fd33ce821e60c78b0cb67a7e8c8bb74269 b/webm_parser/fuzzing/corpus/7770a6fd33ce821e60c78b0cb67a7e8c8bb74269 new file mode 100644 index 0000000..e25e77c --- /dev/null +++ b/webm_parser/fuzzing/corpus/7770a6fd33ce821e60c78b0cb67a7e8c8bb74269 @@ -0,0 +1 @@ +S€g”C¶u u¡Š¦ƒî¦ƒîS€gC¶uŠ ˆu¡…¦ƒ¥!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/781b24e3433ba400df3ccddf56cb8fc7b1fd52ce b/webm_parser/fuzzing/corpus/781b24e3433ba400df3ccddf56cb8fc7b1fd52ce new file mode 100644 index 0000000..536c504 --- /dev/null +++ b/webm_parser/fuzzing/corpus/781b24e3433ba400df3ccddf56cb8fc7b1fd52ce @@ -0,0 +1 @@ +S€gŠI©f…*×±
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/784db9d87b31ffb040ad212e4018b30c3535ad18 b/webm_parser/fuzzing/corpus/784db9d87b31ffb040ad212e4018b30c3535ad18 Binary files differnew file mode 100644 index 0000000..5586a9e --- /dev/null +++ b/webm_parser/fuzzing/corpus/784db9d87b31ffb040ad212e4018b30c3535ad18 diff --git a/webm_parser/fuzzing/corpus/7880e2ae55d255065ad415c4c625e1b63bfd2a93 b/webm_parser/fuzzing/corpus/7880e2ae55d255065ad415c4c625e1b63bfd2a93 new file mode 100644 index 0000000..e2fb6ac --- /dev/null +++ b/webm_parser/fuzzing/corpus/7880e2ae55d255065ad415c4c625e1b63bfd2a93 @@ -0,0 +1 @@ +DzS€g–T®k‘®m€Œb@‰P5ÿ†GçƒGè€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/78cb3d726e4f9e8bc89b399fa514c3b600bf8e5a b/webm_parser/fuzzing/corpus/78cb3d726e4f9e8bc89b399fa514c3b600bf8e5a Binary files differnew file mode 100644 index 0000000..fc42e54 --- /dev/null +++ b/webm_parser/fuzzing/corpus/78cb3d726e4f9e8bc89b399fa514c3b600bf8e5a diff --git a/webm_parser/fuzzing/corpus/79ba60931988a5974328a73fe091bdf6f5992891 b/webm_parser/fuzzing/corpus/79ba60931988a5974328a73fe091bdf6f5992891 Binary files differnew file mode 100644 index 0000000..c6837fd --- /dev/null +++ b/webm_parser/fuzzing/corpus/79ba60931988a5974328a73fe091bdf6f5992891 diff --git a/webm_parser/fuzzing/corpus/79cdb8dec1ad07b389f544a511f89b347429837e b/webm_parser/fuzzing/corpus/79cdb8dec1ad07b389f544a511f89b347429837e Binary files differnew file mode 100644 index 0000000..68e6b7d --- /dev/null +++ b/webm_parser/fuzzing/corpus/79cdb8dec1ad07b389f544a511f89b347429837e diff --git a/webm_parser/fuzzing/corpus/7ab8c6a65c39907f4afa4e2b294303aebb6b22d8 b/webm_parser/fuzzing/corpus/7ab8c6a65c39907f4afa4e2b294303aebb6b22d8 new file mode 100644 index 0000000..fddf510 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7ab8c6a65c39907f4afa4e2b294303aebb6b22d8 @@ -0,0 +1 @@ +°€€€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7b12a398a1860ac2f3930d5020e422aec061f177 b/webm_parser/fuzzing/corpus/7b12a398a1860ac2f3930d5020e422aec061f177 new file mode 100644 index 0000000..fed31c6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7b12a398a1860ac2f3930d5020e422aec061f177 @@ -0,0 +1 @@ +S€gŠT®k…®ƒ¹
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7bf1682743405f3d5b3433830eadae4ea311807d b/webm_parser/fuzzing/corpus/7bf1682743405f3d5b3433830eadae4ea311807d new file mode 100644 index 0000000..d8666eb --- /dev/null +++ b/webm_parser/fuzzing/corpus/7bf1682743405f3d5b3433830eadae4ea311807d @@ -0,0 +1 @@ +S€g»TÃg¶ss³gÈ©È„#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ„D‡dgÈg
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7c7ef0e305f37f833708b375271d59300d120d84 b/webm_parser/fuzzing/corpus/7c7ef0e305f37f833708b375271d59300d120d84 new file mode 100644 index 0000000..c9cedc0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7c7ef0e305f37f833708b375271d59300d120d84 @@ -0,0 +1 @@ +S€gŽT®k‰®‡á…Ÿƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7d06bd4a7de57953ef09c2e18ca67aa7ff367c76 b/webm_parser/fuzzing/corpus/7d06bd4a7de57953ef09c2e18ca67aa7ff367c76 new file mode 100644 index 0000000..6cce7cb --- /dev/null +++ b/webm_parser/fuzzing/corpus/7d06bd4a7de57953ef09c2e18ca67aa7ff367c76 @@ -0,0 +1 @@ +S€gTÃgŠss‡gÈ„E£!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7d31bd53bb7eb7b1f354c5f85d376390760ab16f b/webm_parser/fuzzing/corpus/7d31bd53bb7eb7b1f354c5f85d376390760ab16f Binary files differnew file mode 100644 index 0000000..f356949 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7d31bd53bb7eb7b1f354c5f85d376390760ab16f diff --git a/webm_parser/fuzzing/corpus/7d67ef4d0c681655d4b4e93e848d56865630390b b/webm_parser/fuzzing/corpus/7d67ef4d0c681655d4b4e93e848d56865630390b Binary files differnew file mode 100644 index 0000000..ca4a486 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7d67ef4d0c681655d4b4e93e848d56865630390b diff --git a/webm_parser/fuzzing/corpus/7db9fe869081fdc855913dd000de0d493f5d5192 b/webm_parser/fuzzing/corpus/7db9fe869081fdc855913dd000de0d493f5d5192 Binary files differnew file mode 100644 index 0000000..9fa0f0a --- /dev/null +++ b/webm_parser/fuzzing/corpus/7db9fe869081fdc855913dd000de0d493f5d5192 diff --git a/webm_parser/fuzzing/corpus/7f20c1e73451c3321c30223db91b891753ef77dd b/webm_parser/fuzzing/corpus/7f20c1e73451c3321c30223db91b891753ef77dd new file mode 100644 index 0000000..7f0a6df --- /dev/null +++ b/webm_parser/fuzzing/corpus/7f20c1e73451c3321c30223db91b891753ef77dd @@ -0,0 +1 @@ +S€g‰C¶u„ ‚¡€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/7fd4d999983c3fbf22763853e90ec10b03c70b17 b/webm_parser/fuzzing/corpus/7fd4d999983c3fbf22763853e90ec10b03c70b17 new file mode 100644 index 0000000..65e61e9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/7fd4d999983c3fbf22763853e90ec10b03c70b17 @@ -0,0 +1 @@ +S€g“M›tŽM»„S¬M»„S¬
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/80039ce7eff40359bee041d75e371b55117aad3a b/webm_parser/fuzzing/corpus/80039ce7eff40359bee041d75e371b55117aad3a new file mode 100644 index 0000000..e62a685 --- /dev/null +++ b/webm_parser/fuzzing/corpus/80039ce7eff40359bee041d75e371b55117aad3a @@ -0,0 +1 @@ +ƒ€€€€€ˆ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8104a007bc88a0a8d81ce1fd26d8b2333061e920 b/webm_parser/fuzzing/corpus/8104a007bc88a0a8d81ce1fd26d8b2333061e920 new file mode 100644 index 0000000..1e6fd37 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8104a007bc88a0a8d81ce1fd26d8b2333061e920 @@ -0,0 +1 @@ +S€gœC§p—E¹”¶’Ä€aT€}€€€€€€€€€€ƒèÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/81320c48ed6eea94712c5e8594c0799fbfe30d10 b/webm_parser/fuzzing/corpus/81320c48ed6eea94712c5e8594c0799fbfe30d10 Binary files differnew file mode 100644 index 0000000..28ac48f --- /dev/null +++ b/webm_parser/fuzzing/corpus/81320c48ed6eea94712c5e8594c0799fbfe30d10 diff --git a/webm_parser/fuzzing/corpus/81425ca3d0afb88cc10d412dcc9795905eacd6d9 b/webm_parser/fuzzing/corpus/81425ca3d0afb88cc10d412dcc9795905eacd6d9 new file mode 100644 index 0000000..3afdbfc --- /dev/null +++ b/webm_parser/fuzzing/corpus/81425ca3d0afb88cc10d412dcc9795905eacd6d9 @@ -0,0 +1 @@ +E᜘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/819b4bd08ae7d758990aea8ab9739f3cb97fd42c b/webm_parser/fuzzing/corpus/819b4bd08ae7d758990aea8ab9739f3cb97fd42c Binary files differnew file mode 100644 index 0000000..af77557 --- /dev/null +++ b/webm_parser/fuzzing/corpus/819b4bd08ae7d758990aea8ab9739f3cb97fd42c diff --git a/webm_parser/fuzzing/corpus/8206d92b710c04ce0bd706cec25fbb72014c79bf b/webm_parser/fuzzing/corpus/8206d92b710c04ce0bd706cec25fbb72014c79bf Binary files differnew file mode 100644 index 0000000..a1233fd --- /dev/null +++ b/webm_parser/fuzzing/corpus/8206d92b710c04ce0bd706cec25fbb72014c79bf diff --git a/webm_parser/fuzzing/corpus/822926c528d16c0a9ca4c48a25651f6a0730d797 b/webm_parser/fuzzing/corpus/822926c528d16c0a9ca4c48a25651f6a0730d797 new file mode 100644 index 0000000..f4269d0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/822926c528d16c0a9ca4c48a25651f6a0730d797 @@ -0,0 +1 @@ +S€gŽC§p‰E¹†¶„VT!S€g–C§p‘E¹Ž¶@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/82d80b6051f0750777bdc37319851741144b3671 b/webm_parser/fuzzing/corpus/82d80b6051f0750777bdc37319851741144b3671 new file mode 100644 index 0000000..43b170b --- /dev/null +++ b/webm_parser/fuzzing/corpus/82d80b6051f0750777bdc37319851741144b3671 @@ -0,0 +1 @@ +S€g‹C¶u† „Ž‚è€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/830c73748baaa10c2016d04b408a7f481882cd89 b/webm_parser/fuzzing/corpus/830c73748baaa10c2016d04b408a7f481882cd89 Binary files differnew file mode 100644 index 0000000..11b45e9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/830c73748baaa10c2016d04b408a7f481882cd89 diff --git a/webm_parser/fuzzing/corpus/8315ca46618bd0771353336407396df293e8c182 b/webm_parser/fuzzing/corpus/8315ca46618bd0771353336407396df293e8c182 new file mode 100644 index 0000000..4472080 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8315ca46618bd0771353336407396df293e8c182 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠìƒÌèƒÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/831c152337e8b2993b8cddfde7553a8c9e64631f b/webm_parser/fuzzing/corpus/831c152337e8b2993b8cddfde7553a8c9e64631f Binary files differnew file mode 100644 index 0000000..5387542 --- /dev/null +++ b/webm_parser/fuzzing/corpus/831c152337e8b2993b8cddfde7553a8c9e64631f diff --git a/webm_parser/fuzzing/corpus/832fad0e723027e5bd74726b1722a838183dada7 b/webm_parser/fuzzing/corpus/832fad0e723027e5bd74726b1722a838183dada7 new file mode 100644 index 0000000..3545ab8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/832fad0e723027e5bd74726b1722a838183dada7 @@ -0,0 +1 @@ +S€gTÃgŠss‡gÈ„D„
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/83b78d2ad8afd0729de45b4d9fdd765a949d8073 b/webm_parser/fuzzing/corpus/83b78d2ad8afd0729de45b4d9fdd765a949d8073 new file mode 100644 index 0000000..5cb98fe --- /dev/null +++ b/webm_parser/fuzzing/corpus/83b78d2ad8afd0729de45b4d9fdd765a949d8073 @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶²€ …@helloC|…lang0C~…area0C|…lang1C|C§p·E
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/84a04bf3e15345f5c992bc6732a42b95857631a7 b/webm_parser/fuzzing/corpus/84a04bf3e15345f5c992bc6732a42b95857631a7 new file mode 100644 index 0000000..e89d0d1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/84a04bf3e15345f5c992bc6732a42b95857631a7 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠèƒÌèƒÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/84d3a72c434074ee0d810fb2357047efd23f58f6 b/webm_parser/fuzzing/corpus/84d3a72c434074ee0d810fb2357047efd23f58f6 Binary files differnew file mode 100644 index 0000000..e22f95b --- /dev/null +++ b/webm_parser/fuzzing/corpus/84d3a72c434074ee0d810fb2357047efd23f58f6 diff --git a/webm_parser/fuzzing/corpus/8659fe309298ca90f201ac7ced95b9d0de510eeb b/webm_parser/fuzzing/corpus/8659fe309298ca90f201ac7ced95b9d0de510eeb new file mode 100644 index 0000000..77449c7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8659fe309298ca90f201ac7ced95b9d0de510eeb @@ -0,0 +1 @@ +S€gˆC¶uƒ«
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/866cf21064b9e20eded44e2e096fd9ce6185ce86 b/webm_parser/fuzzing/corpus/866cf21064b9e20eded44e2e096fd9ce6185ce86 Binary files differnew file mode 100644 index 0000000..f3a4ddd --- /dev/null +++ b/webm_parser/fuzzing/corpus/866cf21064b9e20eded44e2e096fd9ce6185ce86 diff --git a/webm_parser/fuzzing/corpus/8864c6c9319ef120d3accb7b70c32c8f5c1ebccb b/webm_parser/fuzzing/corpus/8864c6c9319ef120d3accb7b70c32c8f5c1ebccb Binary files differnew file mode 100644 index 0000000..d6f7c00 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8864c6c9319ef120d3accb7b70c32c8f5c1ebccb diff --git a/webm_parser/fuzzing/corpus/88f01cffe9972bf447a21034c45f6b943ea3ec07 b/webm_parser/fuzzing/corpus/88f01cffe9972bf447a21034c45f6b943ea3ec07 Binary files differnew file mode 100644 index 0000000..97754f4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/88f01cffe9972bf447a21034c45f6b943ea3ec07 diff --git a/webm_parser/fuzzing/corpus/88f15d430469d209b72cd098405efd15a6d18b05 b/webm_parser/fuzzing/corpus/88f15d430469d209b72cd098405efd15a6d18b05 Binary files differnew file mode 100644 index 0000000..adce379 --- /dev/null +++ b/webm_parser/fuzzing/corpus/88f15d430469d209b72cd098405efd15a6d18b05 diff --git a/webm_parser/fuzzing/corpus/8936b35f29084151fa3755d81030386940523c07 b/webm_parser/fuzzing/corpus/8936b35f29084151fa3755d81030386940523c07 new file mode 100644 index 0000000..e39670b --- /dev/null +++ b/webm_parser/fuzzing/corpus/8936b35f29084151fa3755d81030386940523c07 @@ -0,0 +1 @@ +Eߣ„Bó
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/89a42d72ffe0b23309a9da4e15b8854c1cf939ea b/webm_parser/fuzzing/corpus/89a42d72ffe0b23309a9da4e15b8854c1cf939ea new file mode 100644 index 0000000..af09ae1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/89a42d72ffe0b23309a9da4e15b8854c1cf939ea @@ -0,0 +1 @@ +S€g‹S»k†»„³€·€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8a0cf07d2592231bd579e4399538d9f490e3e5b9 b/webm_parser/fuzzing/corpus/8a0cf07d2592231bd579e4399538d9f490e3e5b9 new file mode 100644 index 0000000..ce2a136 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8a0cf07d2592231bd579e4399538d9f490e3e5b9 @@ -0,0 +1 @@ +S€gŒT®k‡®…ჟ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8a5d1c05f894414aa5aaa66cf901c52dfcb119a1 b/webm_parser/fuzzing/corpus/8a5d1c05f894414aa5aaa66cf901c52dfcb119a1 new file mode 100644 index 0000000..c1bfd3b --- /dev/null +++ b/webm_parser/fuzzing/corpus/8a5d1c05f894414aa5aaa66cf901c52dfcb119a1 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8aa514b6e6fef8d045aa049fdb52fa8adcf722d4 b/webm_parser/fuzzing/corpus/8aa514b6e6fef8d045aa049fdb52fa8adcf722d4 Binary files differnew file mode 100644 index 0000000..7d3ecf2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8aa514b6e6fef8d045aa049fdb52fa8adcf722d4 diff --git a/webm_parser/fuzzing/corpus/8b0dcd4400fe19cf6a268af780990f47dba08189 b/webm_parser/fuzzing/corpus/8b0dcd4400fe19cf6a268af780990f47dba08189 new file mode 100644 index 0000000..2e19cb7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8b0dcd4400fe19cf6a268af780990f47dba08189 @@ -0,0 +1 @@ +S€g»TÃg¶ss³gÈ©È„#‡dg)„]‡egÈ„D‡(gÈ„]‡egˆ„D‡gÈ€D‡dgÈg
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8c4655e6528071edd445715ff1559f399dc9d47c b/webm_parser/fuzzing/corpus/8c4655e6528071edd445715ff1559f399dc9d47c Binary files differnew file mode 100644 index 0000000..fe94617 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8c4655e6528071edd445715ff1559f399dc9d47c diff --git a/webm_parser/fuzzing/corpus/8c695cbcc43e37ce25194c4b61b3d87488b308a1 b/webm_parser/fuzzing/corpus/8c695cbcc43e37ce25194c4b61b3d87488b308a1 new file mode 100644 index 0000000..6219e9f --- /dev/null +++ b/webm_parser/fuzzing/corpus/8c695cbcc43e37ce25194c4b61b3d87488b308a1 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P1S€g”T®k®m€Šb@‡P5€GáÿS€g™T®k”®’m€b@ŒP5€P5€Ps€P5€S€g˜T®k“®‘m€Žb@„P1b@„P1S€gT®kˆ®†à„Tº
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8c9dc3c5a839f90f29256ef8c8f1369bc7383817 b/webm_parser/fuzzing/corpus/8c9dc3c5a839f90f29256ef8c8f1369bc7383817 new file mode 100644 index 0000000..85078ca --- /dev/null +++ b/webm_parser/fuzzing/corpus/8c9dc3c5a839f90f29256ef8c8f1369bc7383817 @@ -0,0 +1 @@ +WA¢€€ˆƒ€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8cdcf83eb89d7e5c87cfa1b8811c95f738747a6a b/webm_parser/fuzzing/corpus/8cdcf83eb89d7e5c87cfa1b8811c95f738747a6a new file mode 100644 index 0000000..8db4eec --- /dev/null +++ b/webm_parser/fuzzing/corpus/8cdcf83eb89d7e5c87cfa1b8811c95f738747a6a @@ -0,0 +1 @@ +S€g‰I©f„M€!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8d0c6e6b3d74233685c3a2c1793d1a7b0ae2e34d b/webm_parser/fuzzing/corpus/8d0c6e6b3d74233685c3a2c1793d1a7b0ae2e34d Binary files differnew file mode 100644 index 0000000..b4ef3d5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8d0c6e6b3d74233685c3a2c1793d1a7b0ae2e34d diff --git a/webm_parser/fuzzing/corpus/8d444e8b311d158f854a03ca35d2c7865cd2c46b b/webm_parser/fuzzing/corpus/8d444e8b311d158f854a03ca35d2c7865cd2c46b new file mode 100644 index 0000000..c6c2f15 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8d444e8b311d158f854a03ca35d2c7865cd2c46b @@ -0,0 +1 @@ +E£Eߣ‚
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8dc289730cc03ecbbed57d04cdca538a4fd08059 b/webm_parser/fuzzing/corpus/8dc289730cc03ecbbed57d04cdca538a4fd08059 new file mode 100644 index 0000000..ed9c1ee --- /dev/null +++ b/webm_parser/fuzzing/corpus/8dc289730cc03ecbbed57d04cdca538a4fd08059 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠèƒÌè€Ì
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8dc58cfcb396a60db921901f1b0807c4f4f37619 b/webm_parser/fuzzing/corpus/8dc58cfcb396a60db921901f1b0807c4f4f37619 new file mode 100644 index 0000000..e256d2f --- /dev/null +++ b/webm_parser/fuzzing/corpus/8dc58cfcb396a60db921901f1b0807c4f4f37619 @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI diff --git a/webm_parser/fuzzing/corpus/8e05be75b7c14c463af277559dc8ea7841a733c8 b/webm_parser/fuzzing/corpus/8e05be75b7c14c463af277559dc8ea7841a733c8 new file mode 100644 index 0000000..caff0f1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8e05be75b7c14c463af277559dc8ea7841a733c8 @@ -0,0 +1 @@ +S€gT®kŠ®ˆá†µ„@ÉÛ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8e7eef00a7a6719e77544b57d1d98ff83a37e55a b/webm_parser/fuzzing/corpus/8e7eef00a7a6719e77544b57d1d98ff83a37e55a new file mode 100644 index 0000000..52c1ec4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8e7eef00a7a6719e77544b57d1d98ff83a37e55a @@ -0,0 +1 @@ +S€gŠT®k…®ƒ¹ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8ed6ef9af5555ce4ff12b23139add9621e7ad930 b/webm_parser/fuzzing/corpus/8ed6ef9af5555ce4ff12b23139add9621e7ad930 Binary files differnew file mode 100644 index 0000000..007a500 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8ed6ef9af5555ce4ff12b23139add9621e7ad930 diff --git a/webm_parser/fuzzing/corpus/8f39f83b71d49805a451f81a5a451f5f3d74927d b/webm_parser/fuzzing/corpus/8f39f83b71d49805a451f81a5a451f5f3d74927d Binary files differnew file mode 100644 index 0000000..aea2062 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8f39f83b71d49805a451f81a5a451f5f3d74927d diff --git a/webm_parser/fuzzing/corpus/8f403efbef079f1775b63bcf1061983e4c5d2162 b/webm_parser/fuzzing/corpus/8f403efbef079f1775b63bcf1061983e4c5d2162 new file mode 100644 index 0000000..7a0e070 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8f403efbef079f1775b63bcf1061983e4c5d2162 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gâ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/8f8f956eaaf8f7f5e65002be888b946218f0f7ce b/webm_parser/fuzzing/corpus/8f8f956eaaf8f7f5e65002be888b946218f0f7ce new file mode 100644 index 0000000..7ebec69 --- /dev/null +++ b/webm_parser/fuzzing/corpus/8f8f956eaaf8f7f5e65002be888b946218f0f7ce @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ„€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€!ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€!ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ„€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€!ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶Š¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI diff --git a/webm_parser/fuzzing/corpus/90a03241b5cc656b7c2a4ef55664b0320fa3bd43 b/webm_parser/fuzzing/corpus/90a03241b5cc656b7c2a4ef55664b0320fa3bd43 new file mode 100644 index 0000000..ac88482 --- /dev/null +++ b/webm_parser/fuzzing/corpus/90a03241b5cc656b7c2a4ef55664b0320fa3bd43 @@ -0,0 +1 @@ +S€g”C¶u u¡Š¦ƒî¦€¦€î
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/925a91e7c25a43d9da8b63eba51ed66a52d833aa b/webm_parser/fuzzing/corpus/925a91e7c25a43d9da8b63eba51ed66a52d833aa Binary files differnew file mode 100644 index 0000000..8eac37e --- /dev/null +++ b/webm_parser/fuzzing/corpus/925a91e7c25a43d9da8b63eba51ed66a52d833aa diff --git a/webm_parser/fuzzing/corpus/92de5c217802ec24886240188dca293325eb17c9 b/webm_parser/fuzzing/corpus/92de5c217802ec24886240188dca293325eb17c9 new file mode 100644 index 0000000..8237f0b --- /dev/null +++ b/webm_parser/fuzzing/corpus/92de5c217802ec24886240188dca293325eb17c9 @@ -0,0 +1 @@ +S€g‹T®k†®„Sn!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/939da66e1fc8bb9854865066ee47b6ac4b933aa9 b/webm_parser/fuzzing/corpus/939da66e1fc8bb9854865066ee47b6ac4b933aa9 Binary files differnew file mode 100644 index 0000000..f5179b1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/939da66e1fc8bb9854865066ee47b6ac4b933aa9 diff --git a/webm_parser/fuzzing/corpus/93a91331e7c3c060f461ae1f22e2bbf4747ffe33 b/webm_parser/fuzzing/corpus/93a91331e7c3c060f461ae1f22e2bbf4747ffe33 Binary files differnew file mode 100644 index 0000000..1b28684 --- /dev/null +++ b/webm_parser/fuzzing/corpus/93a91331e7c3c060f461ae1f22e2bbf4747ffe33 diff --git a/webm_parser/fuzzing/corpus/94f6dfc499cbf2e84ffe1d69cb751ada9f2f2e3a b/webm_parser/fuzzing/corpus/94f6dfc499cbf2e84ffe1d69cb751ada9f2f2e3a new file mode 100644 index 0000000..30a5c63 --- /dev/null +++ b/webm_parser/fuzzing/corpus/94f6dfc499cbf2e84ffe1d69cb751ada9f2f2e3a @@ -0,0 +1 @@ +S€gŠC¶u… ƒ›
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/9539fef8ffb9f7c542061f1af1f3f98e9a714cc3 b/webm_parser/fuzzing/corpus/9539fef8ffb9f7c542061f1af1f3f98e9a714cc3 Binary files differnew file mode 100644 index 0000000..0ea9650 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9539fef8ffb9f7c542061f1af1f3f98e9a714cc3 diff --git a/webm_parser/fuzzing/corpus/953c9a616ea56067225c88fccc6423496f2501a6 b/webm_parser/fuzzing/corpus/953c9a616ea56067225c88fccc6423496f2501a6 Binary files differnew file mode 100644 index 0000000..249824c --- /dev/null +++ b/webm_parser/fuzzing/corpus/953c9a616ea56067225c88fccc6423496f2501a6 diff --git a/webm_parser/fuzzing/corpus/95a60268c555a77c1010d75bb44142af47635486 b/webm_parser/fuzzing/corpus/95a60268c555a77c1010d75bb44142af47635486 new file mode 100644 index 0000000..a95f40a --- /dev/null +++ b/webm_parser/fuzzing/corpus/95a60268c555a77c1010d75bb44142af47635486 @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI diff --git a/webm_parser/fuzzing/corpus/95e410025b965cf6ab2e8e2d3559efbe71c1a972 b/webm_parser/fuzzing/corpus/95e410025b965cf6ab2e8e2d3559efbe71c1a972 Binary files differnew file mode 100644 index 0000000..8eddf69 --- /dev/null +++ b/webm_parser/fuzzing/corpus/95e410025b965cf6ab2e8e2d3559efbe71c1a972 diff --git a/webm_parser/fuzzing/corpus/9671f253d1eb7cd504a5617058ce4c01a80c897e b/webm_parser/fuzzing/corpus/9671f253d1eb7cd504a5617058ce4c01a80c897e Binary files differnew file mode 100644 index 0000000..f0b837c --- /dev/null +++ b/webm_parser/fuzzing/corpus/9671f253d1eb7cd504a5617058ce4c01a80c897e diff --git a/webm_parser/fuzzing/corpus/9698d3424f1581a6bbb66c764f14c95a74b87675 b/webm_parser/fuzzing/corpus/9698d3424f1581a6bbb66c764f14c95a74b87675 Binary files differnew file mode 100644 index 0000000..3aedc32 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9698d3424f1581a6bbb66c764f14c95a74b87675 diff --git a/webm_parser/fuzzing/corpus/96f7f38de1f8601311733984cf51cec35500dabe b/webm_parser/fuzzing/corpus/96f7f38de1f8601311733984cf51cec35500dabe new file mode 100644 index 0000000..1230f4c --- /dev/null +++ b/webm_parser/fuzzing/corpus/96f7f38de1f8601311733984cf51cec35500dabe @@ -0,0 +1 @@ +Uª ÿM
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/970ab6333aa5ccf8c2dc14bb56814f7bb4303b94 b/webm_parser/fuzzing/corpus/970ab6333aa5ccf8c2dc14bb56814f7bb4303b94 Binary files differnew file mode 100644 index 0000000..cf0c884 --- /dev/null +++ b/webm_parser/fuzzing/corpus/970ab6333aa5ccf8c2dc14bb56814f7bb4303b94 diff --git a/webm_parser/fuzzing/corpus/98978113d2116dc4bdbb10265fa32bd95230bdb6 b/webm_parser/fuzzing/corpus/98978113d2116dc4bdbb10265fa32bd95230bdb6 new file mode 100644 index 0000000..0da5825 --- /dev/null +++ b/webm_parser/fuzzing/corpus/98978113d2116dc4bdbb10265fa32bd95230bdb6 @@ -0,0 +1 @@ +S€g”C¶u u¡Š¦ƒî¦ƒîS€gC¶uŠ ˆu¡…¦ƒ¥!S€g”C¶u u¡Š¦ƒî¦ƒîS€gC¶uŠ ˆu¡…¦ƒ¥!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/989e2872d2a34de543f23c5061db68212d8258f7 b/webm_parser/fuzzing/corpus/989e2872d2a34de543f23c5061db68212d8258f7 Binary files differnew file mode 100644 index 0000000..3430234 --- /dev/null +++ b/webm_parser/fuzzing/corpus/989e2872d2a34de543f23c5061db68212d8258f7 diff --git a/webm_parser/fuzzing/corpus/98ca9a00ad571c4454fce709d5405e5aca2a363c b/webm_parser/fuzzing/corpus/98ca9a00ad571c4454fce709d5405e5aca2a363c new file mode 100644 index 0000000..938e0bc --- /dev/null +++ b/webm_parser/fuzzing/corpus/98ca9a00ad571c4454fce709d5405e5aca2a363c @@ -0,0 +1 @@ +Eߣ„Bóÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/9965361765a4151902ec04fb21d9247b3a5ed10d b/webm_parser/fuzzing/corpus/9965361765a4151902ec04fb21d9247b3a5ed10d new file mode 100644 index 0000000..6ec73d4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9965361765a4151902ec04fb21d9247b3a5ed10d @@ -0,0 +1 @@ +S€g‘T®kŒ®Šàˆ#ƒã„@ÉÛ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/99d6fe94a50faa50db9d7eb38d74bc3cc8417dc1 b/webm_parser/fuzzing/corpus/99d6fe94a50faa50db9d7eb38d74bc3cc8417dc1 Binary files differnew file mode 100644 index 0000000..0dcb93e --- /dev/null +++ b/webm_parser/fuzzing/corpus/99d6fe94a50faa50db9d7eb38d74bc3cc8417dc1 diff --git a/webm_parser/fuzzing/corpus/9a6869cec3dc84f2051bfaf0ee0d3552aa221f89 b/webm_parser/fuzzing/corpus/9a6869cec3dc84f2051bfaf0ee0d3552aa221f89 Binary files differnew file mode 100644 index 0000000..32ad695 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9a6869cec3dc84f2051bfaf0ee0d3552aa221f89 diff --git a/webm_parser/fuzzing/corpus/9cc1f26de1e3a7df8c7c03b95ff73ce9709c85f1 b/webm_parser/fuzzing/corpus/9cc1f26de1e3a7df8c7c03b95ff73ce9709c85f1 Binary files differnew file mode 100644 index 0000000..5178754 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9cc1f26de1e3a7df8c7c03b95ff73ce9709c85f1 diff --git a/webm_parser/fuzzing/corpus/9cc5b552abbd551485135fa87eab739a0a784057 b/webm_parser/fuzzing/corpus/9cc5b552abbd551485135fa87eab739a0a784057 Binary files differnew file mode 100644 index 0000000..3a95219 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9cc5b552abbd551485135fa87eab739a0a784057 diff --git a/webm_parser/fuzzing/corpus/9cca361865a4fbff75abdbb79c1e91706780576c b/webm_parser/fuzzing/corpus/9cca361865a4fbff75abdbb79c1e91706780576c new file mode 100644 index 0000000..6735729 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9cca361865a4fbff75abdbb79c1e91706780576c @@ -0,0 +1 @@ +S€gŽT®k‰®‡à…°ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/9d8e99f07604d6cb05ef613d41cbfb93b2aff787 b/webm_parser/fuzzing/corpus/9d8e99f07604d6cb05ef613d41cbfb93b2aff787 new file mode 100644 index 0000000..96aa349 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9d8e99f07604d6cb05ef613d41cbfb93b2aff787 @@ -0,0 +1 @@ +S€gŒT®k‡®…àƒšÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/9ecd61eaf2681a882473247a603b9f30c2663d49 b/webm_parser/fuzzing/corpus/9ecd61eaf2681a882473247a603b9f30c2663d49 new file mode 100644 index 0000000..256c70d --- /dev/null +++ b/webm_parser/fuzzing/corpus/9ecd61eaf2681a882473247a603b9f30c2663d49 @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽŠèÿÌèƒÌ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/9f0a3b7c0814b4f80c0745161c8769f63098981b b/webm_parser/fuzzing/corpus/9f0a3b7c0814b4f80c0745161c8769f63098981b Binary files differnew file mode 100644 index 0000000..fe2403b --- /dev/null +++ b/webm_parser/fuzzing/corpus/9f0a3b7c0814b4f80c0745161c8769f63098981b diff --git a/webm_parser/fuzzing/corpus/9f711c29ccf3f54d44000d7ef6299585674be288 b/webm_parser/fuzzing/corpus/9f711c29ccf3f54d44000d7ef6299585674be288 Binary files differnew file mode 100644 index 0000000..2f17a88 --- /dev/null +++ b/webm_parser/fuzzing/corpus/9f711c29ccf3f54d44000d7ef6299585674be288 diff --git a/webm_parser/fuzzing/corpus/9fae60819c28d4fcc88a6a1b93dcf69b4e458203 b/webm_parser/fuzzing/corpus/9fae60819c28d4fcc88a6a1b93dcf69b4e458203 Binary files differnew file mode 100644 index 0000000..9194d9d --- /dev/null +++ b/webm_parser/fuzzing/corpus/9fae60819c28d4fcc88a6a1b93dcf69b4e458203 diff --git a/webm_parser/fuzzing/corpus/a02431cf7c501a5b368c91e41283419d8fa9fb03 b/webm_parser/fuzzing/corpus/a02431cf7c501a5b368c91e41283419d8fa9fb03 Binary files differnew file mode 100644 index 0000000..17706ff --- /dev/null +++ b/webm_parser/fuzzing/corpus/a02431cf7c501a5b368c91e41283419d8fa9fb03 diff --git a/webm_parser/fuzzing/corpus/a0ac6c3c83817637bbbcb11a5106c57aa6654afb b/webm_parser/fuzzing/corpus/a0ac6c3c83817637bbbcb11a5106c57aa6654afb Binary files differnew file mode 100644 index 0000000..a5d2ab4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a0ac6c3c83817637bbbcb11a5106c57aa6654afb diff --git a/webm_parser/fuzzing/corpus/a15fdfa620d19a92d9eaa9f3f13010e53f902796 b/webm_parser/fuzzing/corpus/a15fdfa620d19a92d9eaa9f3f13010e53f902796 new file mode 100644 index 0000000..d5b108f --- /dev/null +++ b/webm_parser/fuzzing/corpus/a15fdfa620d19a92d9eaa9f3f13010e53f902796 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶@…@B€@…XC¶@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a18e76ae792a054c2f6d0d01e0e78d58678b35e3 b/webm_parser/fuzzing/corpus/a18e76ae792a054c2f6d0d01e0e78d58678b35e3 new file mode 100644 index 0000000..135e70d --- /dev/null +++ b/webm_parser/fuzzing/corpus/a18e76ae792a054c2f6d0d01e0e78d58678b35e3 @@ -0,0 +1 @@ +S€gŠT®k…®ƒ†!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a19d04f18f574e561d793ac0dfcffe2b38183eb1 b/webm_parser/fuzzing/corpus/a19d04f18f574e561d793ac0dfcffe2b38183eb1 new file mode 100644 index 0000000..cb84077 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a19d04f18f574e561d793ac0dfcffe2b38183eb1 @@ -0,0 +1 @@ +"µœŸ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a26fb85be3d2bb8a2360bb4d9533a1651bd12d99 b/webm_parser/fuzzing/corpus/a26fb85be3d2bb8a2360bb4d9533a1651bd12d99 Binary files differnew file mode 100644 index 0000000..6fb743b --- /dev/null +++ b/webm_parser/fuzzing/corpus/a26fb85be3d2bb8a2360bb4d9533a1651bd12d99 diff --git a/webm_parser/fuzzing/corpus/a2e860fae30005a9e75b61f54f3d019c44090fcc b/webm_parser/fuzzing/corpus/a2e860fae30005a9e75b61f54f3d019c44090fcc new file mode 100644 index 0000000..124e7d6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a2e860fae30005a9e75b61f54f3d019c44090fcc @@ -0,0 +1 @@ +D…€ðVT€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a3071bfcb7b2fd3c4286ea42e1f7940754b55697 b/webm_parser/fuzzing/corpus/a3071bfcb7b2fd3c4286ea42e1f7940754b55697 Binary files differnew file mode 100644 index 0000000..f16be68 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a3071bfcb7b2fd3c4286ea42e1f7940754b55697 diff --git a/webm_parser/fuzzing/corpus/a4ac408fb9d6def070ad3a76312ca092863048e5 b/webm_parser/fuzzing/corpus/a4ac408fb9d6def070ad3a76312ca092863048e5 new file mode 100644 index 0000000..4e3bc51 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a4ac408fb9d6def070ad3a76312ca092863048e5 @@ -0,0 +1 @@ +Ý
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a4fafc117cbfde8c240deccc8997c7966d9109ba b/webm_parser/fuzzing/corpus/a4fafc117cbfde8c240deccc8997c7966d9109ba new file mode 100644 index 0000000..79af6da --- /dev/null +++ b/webm_parser/fuzzing/corpus/a4fafc117cbfde8c240deccc8997c7966d9109ba @@ -0,0 +1 @@ +S€gŒS»k‡»…³‚
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a59ffb5f6122e45136352585d3b53294a71346d0 b/webm_parser/fuzzing/corpus/a59ffb5f6122e45136352585d3b53294a71346d0 new file mode 100644 index 0000000..b848f43 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a59ffb5f6122e45136352585d3b53294a71346d0 @@ -0,0 +1 @@ +S€g‘C¶uŒ Š¡‡¦…îƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a5b3a3c48727c26dfd625f247069d2cdbfa031f0 b/webm_parser/fuzzing/corpus/a5b3a3c48727c26dfd625f247069d2cdbfa031f0 Binary files differnew file mode 100644 index 0000000..89ee740 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a5b3a3c48727c26dfd625f247069d2cdbfa031f0 diff --git a/webm_parser/fuzzing/corpus/a5c829fbcd9fd760bc55bc8ab6901b8d401b65f6 b/webm_parser/fuzzing/corpus/a5c829fbcd9fd760bc55bc8ab6901b8d401b65f6 new file mode 100644 index 0000000..13dc200 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a5c829fbcd9fd760bc55bc8ab6901b8d401b65f6 @@ -0,0 +1 @@ +S€gŠT®k…®ƒƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a5fbbce038cad4f5e0c0f97fa69ebc3601123e5c b/webm_parser/fuzzing/corpus/a5fbbce038cad4f5e0c0f97fa69ebc3601123e5c Binary files differnew file mode 100644 index 0000000..ec1eabd --- /dev/null +++ b/webm_parser/fuzzing/corpus/a5fbbce038cad4f5e0c0f97fa69ebc3601123e5c diff --git a/webm_parser/fuzzing/corpus/a6e57b33e7a219168280e51bc98a44de40f0f9ca b/webm_parser/fuzzing/corpus/a6e57b33e7a219168280e51bc98a44de40f0f9ca new file mode 100644 index 0000000..d8f16c2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a6e57b33e7a219168280e51bc98a44de40f0f9ca @@ -0,0 +1 @@ +S€g‹S»k†»„·€·€S€g‹S»k†»„·€·€Eߣˆb‚@webE
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a78afe9e4e9f02a10ebadac64171ad49749a6965 b/webm_parser/fuzzing/corpus/a78afe9e4e9f02a10ebadac64171ad49749a6965 new file mode 100644 index 0000000..1b8c641 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a78afe9e4e9f02a10ebadac64171ad49749a6965 @@ -0,0 +1 @@ +S€gˆM›tƒM»€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a823a019c0b19c97a1d35722cd843109ab016c17 b/webm_parser/fuzzing/corpus/a823a019c0b19c97a1d35722cd843109ab016c17 new file mode 100644 index 0000000..eae1319 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a823a019c0b19c97a1d35722cd843109ab016c17 @@ -0,0 +1,22 @@ +Eߣ€S€g †¡C§p †šE¹ †•¶ †‘¶ †¶ †‰¶ †…¶ †¶ †}¶ †y¶ †u¶ †q¶ †m¶ †i¶ †e¶ †a¶ †]¶ †Y¶ †U¶ †Q¶ †M¶ †I¶ †E¶ †A¶ †=¶ †9¶ †5¶ †1¶ †-¶ †)¶ †%¶ †!¶ †¶ †¶ †¶ †¶ †
¶ † ¶ †¶ †¶ …ý¶ …ù¶ …õ¶ …ñ¶ …í¶ …é¶ …å¶ …ᶠ…ݶ …Ù¶ …Õ¶ …Ѷ …Ͷ …ɶ …Ŷ …Á¶ …½¶ …¹¶ …µ¶ …±¶ …¶ …©¶ …¥¶ …¡¶ …¶ …™¶ …•¶ …‘¶ …¶ …‰¶ ……¶ …¶ …}¶ …y¶ …u¶ …q¶ …m¶ …i¶ …e¶ …a¶ …]¶ …Y¶ …U¶ …Q¶ …M¶ …I¶ …E¶ …A¶ …=¶ …9¶ …5¶ …1¶ …-¶ …)¶ …%¶ …!¶ …¶ …¶ …¶ …¶ …
¶ … ¶ …¶ …¶ „ý¶ „ù¶ „õ¶ „ñ¶ „í¶ „é¶ „å¶ „ᶠ„ݶ „Ù¶ „Õ¶ „Ѷ „Ͷ „ɶ „Ŷ „Á¶ „½¶ „¹¶ „µ¶ „±¶ „¶ „©¶ „¥¶ „¡¶ „¶ „™¶ „•¶ „‘¶ „¶ „‰¶ „…¶ „¶ „}¶ „y¶ „u¶ „q¶ „m¶ „i¶ „e¶ „a¶ „]¶ „Y¶ „U¶ „Q¶ „M¶ „I¶ „E¶ „A¶ „=¶ „9¶ „5¶ „1¶ „-¶ „)¶ „%¶ „!¶ „¶ „¶ „¶ „¶ „
¶ „ ¶ „¶ „¶ ƒý¶ ƒù¶ ƒõ¶ ƒñ¶ ƒí¶ ƒé¶ ƒå¶ ƒá¶ ƒÝ¶ ƒÙ¶ ƒÕ¶ ƒÑ¶ ƒÍ¶ ƒÉ¶ ƒÅ¶ ƒÁ¶ ƒ½¶ ƒ¹¶ ƒµ¶ ƒ±¶ ƒ¶ ƒ©¶ ƒ¥¶ ƒ¡¶ ƒ¶ ƒ™¶ ƒ•¶ ƒ‘¶ ƒ¶ ƒ‰¶ ƒ…¶ ƒ¶ ƒ}¶ ƒy¶ ƒu¶ ƒq¶ ƒm¶ ƒi¶ ƒe¶ ƒa¶ ƒ]¶ ƒY¶ ƒU¶ ƒQ¶ ƒM¶ ƒI¶ ƒE¶ ƒA¶ ƒ=¶ ƒ9¶ ƒ5¶ ƒ1¶ ƒ-¶ ƒ)¶ ƒ%¶ ƒ!¶ ƒ¶ ƒ¶ ƒ¶ ƒ¶ ƒ
¶ ƒ ¶ ƒ¶ ƒ¶ ‚ý¶ ‚ù¶ ‚õ¶ ‚ñ¶ ‚í¶ ‚é¶ ‚å¶ ‚ᶠ‚ݶ ‚Ù¶ ‚Õ¶ ‚Ѷ ‚Ͷ ‚ɶ ‚Ŷ ‚Á¶ ‚½¶ ‚¹¶ ‚µ¶ ‚±¶ ‚¶ ‚©¶ ‚¥¶ ‚¡¶ ‚¶ ‚™¶ ‚•¶ ‚‘¶ ‚¶ ‚‰¶ ‚…¶ ‚¶ ‚}¶ ‚y¶ ‚u¶ ‚q¶ ‚m¶ ‚i¶ ‚e¶ ‚a¶ ‚]¶ ‚Y¶ ‚U¶ ‚Q¶ ‚M¶ ‚I¶ ‚E¶ ‚A¶ ‚=¶ ‚9¶ ‚5¶ ‚1¶ ‚-¶ ‚)¶ ‚%¶ ‚!¶ ‚¶ ‚¶ ‚¶ ‚¶ ‚
¶ ‚ ¶ ‚¶ ‚¶ ý¶ ù¶ õ¶ ñ¶ í¶ é¶ å¶ á¶ Ý¶ Ù¶ Õ¶ Ѷ Ͷ ɶ Ŷ Á¶ ½¶ ¹¶ µ¶ ±¶ ¶ ©¶ ¥¶ ¡¶ ¶ ™¶ •¶ ‘¶ ¶ ‰¶ …¶ ¶ }¶ y¶ u¶ q¶ m¶ i¶ e¶ a¶ ]¶ Y¶ U¶ Q¶ M¶ I¶ E¶ A¶ =¶ 9¶ 5¶ 1¶ -¶ )¶ %¶ !¶ ¶ ¶ ¶ ¶
¶ ¶ ¶ ¶ €ý¶ €ù¶ €õ¶ €ñ¶ €í¶ €é¶ €å¶ €á¶ €Ý¶ €Ù¶ €Õ¶ €Ñ¶ €Í¶ €É¶ €Å¶ €Á¶ €½¶ €¹¶ €µ¶ €±¶ €¶ €©¶ €¥¶ €¡¶ €¶ €™¶ €•¶ €‘¶ €¶ €‰¶ €…¶ €¶ €}¶ €y¶ €u¶ €q¶ €m¶ €i¶ €e¶ €a¶ €]¶ €Y¶ €U¶ €Q¶ €M¶ €I¶ €E¶ €A¶ €=¶ €9¶ €5¶ €1¶ €-¶ €)¶ €%¶ €!¶ €¶ €¶ €¶ €¶ €
¶ € ¶ €¶ €¶ ý¶ ù¶ õ¶ ñ¶ í¶ é¶ å¶ á¶ Ý¶ Ù¶ Õ¶ Ѷ Ͷ ɶ Ŷ Á¶ ½¶ ¹¶ µ¶ ±¶ ¶ ©¶ ¥¶ ¡¶ ¶ ™¶ •¶ ‘¶ ¶ ‰¶ …¶ ¶ }¶ y¶ u¶ q¶ m¶ i¶ e¶ a¶ ]¶ Y¶ U¶ Q¶ M¶ I¶ E¶ A¶ =¶ 9¶ 5¶ 1¶ -¶ )¶ %¶ !¶ ¶ ¶ ¶ ¶
¶ ¶ ¶ ¶ ~ý¶ ~ù¶ ~õ¶ ~ñ¶ ~í¶ ~é¶ ~å¶ ~ᶠ~ݶ ~Ù¶ ~Õ¶ ~Ѷ ~Ͷ ~ɶ ~Ŷ ~Á¶ ~½¶ ~¹¶ ~µ¶ ~±¶ ~¶ ~©¶ ~¥¶ ~¡¶ ~¶ ~™¶ ~•¶ ~‘¶ ~¶ ~‰¶ ~…¶ ~¶ ~}¶ ~y¶ ~u¶ ~q¶ ~m¶ ~i¶ ~e¶ ~a¶ ~]¶ ~Y¶ ~U¶ ~Q¶ ~M¶ ~I¶ ~E¶ ~A¶ ~=¶ ~9¶ ~5¶ ~1¶ ~-¶ ~)¶ ~%¶ ~!¶ ~¶ ~¶ ~¶ ~¶ ~
¶ ~ ¶ ~¶ ~¶ }ý¶ }ù¶ }õ¶ }ñ¶ }í¶ }é¶ }å¶ }ᶠ}ݶ }Ù¶ }Õ¶ }Ѷ }Ͷ }ɶ }Ŷ }Á¶ }½¶ }¹¶ }µ¶ }±¶ }¶ }©¶ }¥¶ }¡¶ }¶ }™¶ }•¶ }‘¶ }¶ }‰¶ }…¶ }¶ }}¶ }y¶ }u¶ }q¶ }m¶ }i¶ }e¶ }a¶ }]¶ }Y¶ }U¶ }Q¶ }M¶ }I¶ }E¶ }A¶ }=¶ }9¶ }5¶ }1¶ }-¶ })¶ }%¶ }!¶ }¶ }¶ }¶ }¶ }
¶ } ¶ }¶ }¶ |ý¶ |ù¶ |õ¶ |ñ¶ |í¶ |é¶ |å¶ |ᶠ|ݶ |Ù¶ |Õ¶ |Ѷ |Ͷ |ɶ |Ŷ |Á¶ |½¶ |¹¶ |µ¶ |±¶ |¶ |©¶ |¥¶ |¡¶ |¶ |™¶ |•¶ |‘¶ |¶ |‰¶ |…¶ |¶ |}¶ |y¶ |u¶ |q¶ |m¶ |i¶ |e¶ |a¶ |]¶ |Y¶ |U¶ |Q¶ |M¶ |I¶ |E¶ |A¶ |=¶ |9¶ |5¶ |1¶ |-¶ |)¶ |%¶ |!¶ |¶ |¶ |¶ |¶ |
¶ | ¶ |¶ |¶ {ý¶ {ù¶ {õ¶ {ñ¶ {í¶ {é¶ {å¶ {ᶠ{ݶ {Ù¶ {Õ¶ {Ѷ {Ͷ {ɶ {Ŷ {Á¶ {½¶ {¹¶ {µ¶ {±¶ {¶ {©¶ {¥¶ {¡¶ {¶ {™¶ {•¶ {‘¶ {¶ {‰¶ {…¶ {¶ {}¶ {y¶ {u¶ {q¶ {m¶ {i¶ {e¶ {a¶ {]¶ {Y¶ {U¶ {Q¶ {M¶ {I¶ {E¶ {A¶ {=¶ {9¶ {5¶ {1¶ {-¶ {)¶ {%¶ {!¶ {¶ {¶ {¶ {¶ {
¶ { ¶ {¶ {¶ zý¶ zù¶ zõ¶ zñ¶ zí¶ zé¶ zå¶ zᶠzݶ zÙ¶ zÕ¶ zѶ zͶ zɶ zŶ zÁ¶ z½¶ z¹¶ zµ¶ z±¶ z¶ z©¶ z¥¶ z¡¶ z¶ z™¶ z•¶ z‘¶ z¶ z‰¶ z…¶ z¶ z}¶ zy¶ zu¶ zq¶ zm¶ zi¶ ze¶ za¶ z]¶ zY¶ zU¶ zQ¶ zM¶ zI¶ zE¶ zA¶ z=¶ z9¶ z5¶ z1¶ z-¶ z)¶ z%¶ z!¶ z¶ z¶ z¶ z¶ z
¶ z ¶ z¶ z¶ yý¶ yù¶ yõ¶ yñ¶ yí¶ yé¶ yå¶ yᶠyݶ yÙ¶ yÕ¶ yѶ yͶ yɶ yŶ yÁ¶ y½¶ y¹¶ yµ¶ y±¶ y¶ y©¶ y¥¶ y¡¶ y¶ y™¶ y•¶ y‘¶ y¶ y‰¶ y…¶ y¶ y}¶ yy¶ yu¶ yq¶ ym¶ yi¶ ye¶ ya¶ y]¶ yY¶ yU¶ yQ¶ yM¶ yI¶ yE¶ yA¶ y=¶ y9¶ y5¶ y1¶ y-¶ y)¶ y%¶ y!¶ y¶ y¶ y¶ y¶ y
¶ y ¶ y¶ y¶ xý¶ xù¶ xõ¶ xñ¶ xí¶ xé¶ xå¶ xᶠxݶ xÙ¶ xÕ¶ xѶ xͶ xɶ xŶ xÁ¶ x½¶ x¹¶ xµ¶ x±¶ x¶ x©¶ x¥¶ x¡¶ x¶ x™¶ x•¶ x‘¶ x¶ x‰¶ x…¶ x¶ x}¶ xy¶ xu¶ xq¶ xm¶ xi¶ xe¶ xa¶ x]¶ xY¶ xU¶ xQ¶ xM¶ xI¶ xE¶ xA¶ x=¶ x9¶ x5¶ x1¶ x-¶ x)¶ x%¶ x!¶ x¶ x¶ x¶ x¶ x
¶ x ¶ x¶ x¶ wý¶ wù¶ wõ¶ wñ¶ wí¶ wé¶ wå¶ wᶠwݶ wÙ¶ wÕ¶ wѶ wͶ wɶ wŶ wÁ¶ w½¶ w¹¶ wµ¶ w±¶ w¶ w©¶ w¥¶ w¡¶ w¶ w™¶ w•¶ w‘¶ w¶ w‰¶ w…¶ w¶ w}¶ wy¶ wu¶ wq¶ wm¶ wi¶ we¶ wa¶ w]¶ wY¶ wU¶ wQ¶ wM¶ wI¶ wE¶ wA¶ w=¶ w9¶ w5¶ w1¶ w-¶ w)¶ w%¶ w!¶ w¶ w¶ w¶ w¶ w
¶ w ¶ w¶ w¶ vý¶ vù¶ võ¶ vñ¶ ví¶ vé¶ vå¶ vᶠvݶ vÙ¶ vÕ¶ vѶ vͶ vɶ vŶ vÁ¶ v½¶ v¹¶ vµ¶ v±¶ v¶ v©¶ v¥¶ v¡¶ v¶ v™¶ v•¶ v‘¶ v¶ v‰¶ v…¶ v¶ v}¶ vy¶ vu¶ vq¶ vm¶ vi¶ ve¶ va¶ v]¶ vY¶ vU¶ vQ¶ vM¶ vI¶ vE¶ vA¶ v=¶ v9¶ v5¶ v1¶ v-¶ v)¶ v%¶ v!¶ v¶ v¶ v¶ v¶ v
¶ v ¶ v¶ v¶ uý¶ uù¶ uõ¶ uñ¶ uí¶ ué¶ uå¶ uᶠuݶ uÙ¶ uÕ¶ uѶ uͶ uɶ uŶ uÁ¶ u½¶ u¹¶ uµ¶ u±¶ u¶ u©¶ u¥¶ u¡¶ u¶ u™¶ u•¶ u‘¶ u¶ u‰¶ u…¶ u¶ u}¶ uy¶ uu¶ uq¶ um¶ ui¶ ue¶ ua¶ u]¶ uY¶ uU¶ uQ¶ uM¶ uI¶ uE¶ uA¶ u=¶ u9¶ u5¶ u1¶ u-¶ u)¶ u%¶ u!¶ u¶ u¶ u¶ u¶ u
¶ u ¶ u¶ u¶ tý¶ tù¶ tõ¶ tñ¶ tí¶ té¶ tå¶ tᶠtݶ tÙ¶ tÕ¶ tѶ tͶ tɶ tŶ tÁ¶ t½¶ t¹¶ tµ¶ t±¶ t¶ t©¶ t¥¶ t¡¶ t¶ t™¶ t•¶ t‘¶ t¶ t‰¶ t…¶ t¶ t}¶ ty¶ tu¶ tq¶ tm¶ ti¶ te¶ ta¶ t]¶ tY¶ tU¶ tQ¶ tM¶ tI¶ tE¶ tA¶ t=¶ t9¶ t5¶ t1¶ t-¶ t)¶ t%¶ t!¶ t¶ t¶ t¶ t¶ t
¶ t ¶ t¶ t¶ sý¶ sù¶ sõ¶ sñ¶ sí¶ sé¶ så¶ sᶠsݶ sÙ¶ sÕ¶ sѶ sͶ sɶ sŶ sÁ¶ s½¶ s¹¶ sµ¶ s±¶ s¶ s©¶ s¥¶ s¡¶ s¶ s™¶ s•¶ s‘¶ s¶ s‰¶ s…¶ s¶ s}¶ sy¶ su¶ sq¶ sm¶ si¶ se¶ sa¶ s]¶ sY¶ sU¶ sQ¶ sM¶ sI¶ sE¶ sA¶ s=¶ s9¶ s5¶ s1¶ s-¶ s)¶ s%¶ s!¶ s¶ s¶ s¶ s¶ s
¶ s ¶ s¶ s¶ rý¶ rù¶ rõ¶ rñ¶ rí¶ ré¶ rå¶ rᶠrݶ rÙ¶ rÕ¶ rѶ rͶ rɶ rŶ rÁ¶ r½¶ r¹¶ rµ¶ r±¶ r¶ r©¶ r¥¶ r¡¶ r¶ r™¶ r•¶ r‘¶ r¶ r‰¶ r…¶ r¶ r}¶ ry¶ ru¶ rq¶ rm¶ ri¶ re¶ ra¶ r]¶ rY¶ rU¶ rQ¶ rM¶ rI¶ rE¶ rA¶ r=¶ r9¶ r5¶ r1¶ r-¶ r)¶ r%¶ r!¶ r¶ r¶ r¶ r¶ r
¶ r ¶ r¶ r¶ qý¶ qù¶ qõ¶ qñ¶ qí¶ qé¶ qå¶ qᶠqݶ qÙ¶ qÕ¶ qѶ qͶ qɶ qŶ qÁ¶ q½¶ q¹¶ qµ¶ q±¶ q¶ q©¶ q¥¶ q¡¶ q¶ q™¶ q•¶ q‘¶ q¶ q‰¶ q…¶ q¶ q}¶ qy¶ qu¶ qq¶ qm¶ qi¶ qe¶ qa¶ q]¶ qY¶ qU¶ qQ¶ qM¶ qI¶ qE¶ qA¶ q=¶ q9¶ q5¶ q1¶ q-¶ q)¶ q%¶ q!¶ q¶ q¶ q¶ q¶ q
¶ q ¶ q¶ q¶ pý¶ pù¶ põ¶ pñ¶ pí¶ pé¶ på¶ pᶠpݶ pÙ¶ pÕ¶ pѶ pͶ pɶ pŶ pÁ¶ p½¶ p¹¶ pµ¶ p±¶ p¶ p©¶ p¥¶ p¡¶ p¶ p™¶ p•¶ p‘¶ p¶ p‰¶ p…¶ p¶ p}¶ py¶ pu¶ pq¶ pm¶ pi¶ pe¶ pa¶ p]¶ pY¶ pU¶ pQ¶ pM¶ pI¶ pE¶ pA¶ p=¶ p9¶ p5¶ p1¶ p-¶ p)¶ p%¶ p!¶ p¶ p¶ p¶ p¶ p
¶ p ¶ p¶ p¶ oý¶ où¶ oõ¶ oñ¶ oí¶ oé¶ oå¶ oᶠoݶ oÙ¶ oÕ¶ oѶ oͶ oɶ oŶ oÁ¶ o½¶ o¹¶ oµ¶ o±¶ o¶ o©¶ o¥¶ o¡¶ o¶ o™¶ o•¶ o‘¶ o¶ o‰¶ o…¶ o¶ o}¶ oy¶ ou¶ oq¶ om¶ oi¶ oe¶ oa¶ o]¶ oY¶ oU¶ oQ¶ oM¶ oI¶ oE¶ oA¶ o=¶ o9¶ o5¶ o1¶ o-¶ o)¶ o%¶ o!¶ o¶ o¶ o¶ o¶ o
¶ o ¶ o¶ o¶ ný¶ nù¶ nõ¶ nñ¶ ní¶ né¶ nå¶ nᶠnݶ nÙ¶ nÕ¶ nѶ nͶ nɶ nŶ nÁ¶ n½¶ n¹¶ nµ¶ n±¶ n¶ n©¶ n¥¶ n¡¶ n¶ n™¶ n•¶ n‘¶ n¶ n‰¶ n…¶ n¶ n}¶ ny¶ nu¶ nq¶ nm¶ ni¶ ne¶ na¶ n]¶ nY¶ nU¶ nQ¶ nM¶ nI¶ nE¶ nA¶ n=¶ n9¶ n5¶ n1¶ n-¶ n)¶ n%¶ n!¶ n¶ n¶ n¶ n¶ n
¶ n ¶ n¶ n¶ mý¶ mù¶ mõ¶ mñ¶ mí¶ mé¶ må¶ mᶠmݶ mÙ¶ mÕ¶ mѶ mͶ mɶ mŶ mÁ¶ m½¶ m¹¶ mµ¶ m±¶ m¶ m©¶ m¥¶ m¡¶ m¶ m™¶ m•¶ m‘¶ m¶ m‰¶ m…¶ m¶ m}¶ my¶ mu¶ mq¶ mm¶ mi¶ me¶ ma¶ m]¶ mY¶ mU¶ mQ¶ mM¶ mI¶ mE¶ mA¶ m=¶ m9¶ m5¶ m1¶ m-¶ m)¶ m%¶ m!¶ m¶ m¶ m¶ m¶ m
¶ m ¶ m¶ m¶ lý¶ lù¶ lõ¶ lñ¶ lí¶ lé¶ lå¶ lᶠlݶ lÙ¶ lÕ¶ lѶ lͶ lɶ lŶ lÁ¶ l½¶ l¹¶ lµ¶ l±¶ l¶ l©¶ l¥¶ l¡¶ l¶ l™¶ l•¶ l‘¶ l¶ l‰¶ l…¶ l¶ l}¶ ly¶ lu¶ lq¶ lm¶ li¶ le¶ la¶ l]¶ lY¶ lU¶ lQ¶ lM¶ lI¶ lE¶ lA¶ l=¶ l9¶ l5¶ l1¶ l-¶ l)¶ l%¶ l!¶ l¶ l¶ l¶ l¶ l
¶ l ¶ l¶ l¶ ký¶ kù¶ kõ¶ kñ¶ kí¶ ké¶ kå¶ kᶠkݶ kÙ¶ kÕ¶ kѶ kͶ kɶ kŶ kÁ¶ k½¶ k¹¶ kµ¶ k±¶ k¶ k©¶ k¥¶ k¡¶ k¶ k™¶ k•¶ k‘¶ k¶ k‰¶ k…¶ k¶ k}¶ ky¶ ku¶ kq¶ km¶ ki¶ ke¶ ka¶ k]¶ kY¶ kU¶ kQ¶ kM¶ kI¶ kE¶ kA¶ k=¶ k9¶ k5¶ k1¶ k-¶ k)¶ k%¶ k!¶ k¶ k¶ k¶ k¶ k
¶ k ¶ k¶ k¶ jý¶ jù¶ jõ¶ jñ¶ jí¶ jé¶ jå¶ jᶠjݶ jÙ¶ jÕ¶ jѶ jͶ jɶ jŶ jÁ¶ j½¶ j¹¶ jµ¶ j±¶ j¶ j©¶ j¥¶ j¡¶ j¶ j™¶ j•¶ j‘¶ j¶ j‰¶ j…¶ j¶ j}¶ jy¶ ju¶ jq¶ jm¶ ji¶ je¶ ja¶ j]¶ jY¶ jU¶ jQ¶ jM¶ jI¶ jE¶ jA¶ j=¶ j9¶ j5¶ j1¶ j-¶ j)¶ j%¶ j!¶ j¶ j¶ j¶ j¶ j
¶ j ¶ j¶ j¶ iý¶ iù¶ iõ¶ iñ¶ ií¶ ié¶ iå¶ iᶠiݶ iÙ¶ iÕ¶ iѶ iͶ iɶ iŶ iÁ¶ i½¶ i¹¶ iµ¶ i±¶ i¶ i©¶ i¥¶ i¡¶ i¶ i™¶ i•¶ i‘¶ i¶ i‰¶ i…¶ i¶ i}¶ iy¶ iu¶ iq¶ im¶ ii¶ ie¶ ia¶ i]¶ iY¶ iU¶ iQ¶ iM¶ iI¶ iE¶ iA¶ i=¶ i9¶ i5¶ i1¶ i-¶ i)¶ i%¶ i!¶ i¶ i¶ i¶ i¶ i
¶ i ¶ i¶ i¶ hý¶ hù¶ hõ¶ hñ¶ hí¶ hé¶ hå¶ hᶠhݶ hÙ¶ hÕ¶ hѶ hͶ hɶ hŶ hÁ¶ h½¶ h¹¶ hµ¶ h±¶ h¶ h©¶ h¥¶ h¡¶ h¶ h™¶ h•¶ h‘¶ h¶ h‰¶ h…¶ h¶ h}¶ hy¶ hu¶ hq¶ hm¶ hi¶ he¶ ha¶ h]¶ hY¶ hU¶ hQ¶ hM¶ hI¶ hE¶ hA¶ h=¶ h9¶ h5¶ h1¶ h-¶ h)¶ h%¶ h!¶ h¶ h¶ h¶ h¶ h
¶ h ¶ h¶ h¶ gý¶ gù¶ gõ¶ gñ¶ gí¶ gé¶ gå¶ gᶠgݶ gÙ¶ gÕ¶ gѶ gͶ gɶ gŶ gÁ¶ g½¶ g¹¶ gµ¶ g±¶ g¶ g©¶ g¥¶ g¡¶ g¶ g™¶ g•¶ g‘¶ g¶ g‰¶ g…¶ g¶ g}¶ gy¶ gu¶ gq¶ gm¶ gi¶ ge¶ ga¶ g]¶ gY¶ gU¶ gQ¶ gM¶ gI¶ gE¶ gA¶ g=¶ g9¶ g5¶ g1¶ g-¶ g)¶ g%¶ g!¶ g¶ g¶ g¶ g¶ g
¶ g ¶ g¶ g¶ fý¶ fù¶ fõ¶ fñ¶ fí¶ fé¶ få¶ fᶠfݶ fÙ¶ fÕ¶ fѶ fͶ fɶ fŶ fÁ¶ f½¶ f¹¶ fµ¶ f±¶ f¶ f©¶ f¥¶ f¡¶ f¶ f™¶ f•¶ f‘¶ f¶ f‰¶ f…¶ f¶ f}¶ fy¶ fu¶ fq¶ fm¶ fi¶ fe¶ fa¶ f]¶ fY¶ fU¶ fQ¶ fM¶ fI¶ fE¶ fA¶ f=¶ f9¶ f5¶ f1¶ f-¶ f)¶ f%¶ f!¶ f¶ f¶ f¶ f¶ f
¶ f ¶ f¶ f¶ eý¶ eù¶ eõ¶ eñ¶ eí¶ eé¶ eå¶ eᶠeݶ eÙ¶ eÕ¶ eѶ eͶ eɶ eŶ eÁ¶ e½¶ e¹¶ eµ¶ e±¶ e¶ e©¶ e¥¶ e¡¶ e¶ e™¶ e•¶ e‘¶ e¶ e‰¶ e…¶ e¶ e}¶ ey¶ eu¶ eq¶ em¶ ei¶ ee¶ ea¶ e]¶ eY¶ eU¶ eQ¶ eM¶ eI¶ eE¶ eA¶ e=¶ e9¶ e5¶ e1¶ e-¶ e)¶ e%¶ e!¶ e¶ e¶ e¶ e¶ e
¶ e ¶ e¶ e¶ dý¶ dù¶ dõ¶ dñ¶ dí¶ dé¶ då¶ dᶠdݶ dÙ¶ dÕ¶ dѶ dͶ dɶ dŶ dÁ¶ d½¶ d¹¶ dµ¶ d±¶ d¶ d©¶ d¥¶ d¡¶ d¶ d™¶ d•¶ d‘¶ d¶ d‰¶ d…¶ d¶ d}¶ dy¶ du¶ dq¶ dm¶ di¶ de¶ da¶ d]¶ dY¶ dU¶ dQ¶ dM¶ dI¶ dE¶ dA¶ d=¶ d9¶ d5¶ d1¶ d-¶ d)¶ d%¶ d!¶ d¶ d¶ d¶ d¶ d
¶ d ¶ d¶ d¶ cý¶ cù¶ cõ¶ cñ¶ cí¶ cé¶ cå¶ cᶠcݶ cÙ¶ cÕ¶ cѶ cͶ cɶ cŶ cÁ¶ c½¶ c¹¶ cµ¶ c±¶ c¶ c©¶ c¥¶ c¡¶ c¶ c™¶ c•¶ c‘¶ c¶ c‰¶ c…¶ c¶ c}¶ cy¶ cu¶ cq¶ cm¶ ci¶ ce¶ ca¶ c]¶ cY¶ cU¶ cQ¶ cM¶ cI¶ cE¶ cA¶ c=¶ c9¶ c5¶ c1¶ c-¶ c)¶ c%¶ c!¶ c¶ c¶ c¶ c¶ c
¶ c ¶ c¶ c¶ bý¶ bù¶ bõ¶ bñ¶ bí¶ bé¶ bå¶ bᶠbݶ bÙ¶ bÕ¶ bѶ bͶ bɶ bŶ bÁ¶ b½¶ b¹¶ bµ¶ b±¶ b¶ b©¶ b¥¶ b¡¶ b¶ b™¶ b•¶ b‘¶ b¶ b‰¶ b…¶ b¶ b}¶ by¶ bu¶ bq¶ bm¶ bi¶ be¶ ba¶ b]¶ bY¶ bU¶ bQ¶ bM¶ bI¶ bE¶ bA¶ b=¶ b9¶ b5¶ b1¶ b-¶ b)¶ b%¶ b!¶ b¶ b¶ b¶ b¶ b
¶ b ¶ b¶ b¶ aý¶ aù¶ aõ¶ añ¶ aí¶ aé¶ aå¶ aᶠaݶ aÙ¶ aÕ¶ aѶ aͶ aɶ aŶ aÁ¶ a½¶ a¹¶ aµ¶ a±¶ a¶ a©¶ a¥¶ a¡¶ a¶ a™¶ a•¶ a‘¶ a¶ a‰¶ a…¶ a¶ a}¶ ay¶ au¶ aq¶ am¶ ai¶ ae¶ aa¶ a]¶ aY¶ aU¶ aQ¶ aM¶ aI¶ aE¶ aA¶ a=¶ a9¶ a5¶ a1¶ a-¶ a)¶ a%¶ a!¶ a¶ a¶ a¶ a¶ a
¶ a ¶ a¶ a¶ `ý¶ `ù¶ `õ¶ `ñ¶ `í¶ `é¶ `å¶ `ᶠ`ݶ `Ù¶ `Õ¶ `Ѷ `Ͷ `ɶ `Ŷ `Á¶ `½¶ `¹¶ `µ¶ `±¶ `¶ `©¶ `¥¶ `¡¶ `¶ `™¶ `•¶ `‘¶ `¶ `‰¶ `…¶ `¶ `}¶ `y¶ `u¶ `q¶ `m¶ `i¶ `e¶ `a¶ `]¶ `Y¶ `U¶ `Q¶ `M¶ `I¶ `E¶ `A¶ `=¶ `9¶ `5¶ `1¶ `-¶ `)¶ `%¶ `!¶ `¶ `¶ `¶ `¶ `
¶ ` ¶ `¶ `¶ _ý¶ _ù¶ _õ¶ _ñ¶ _í¶ _é¶ _å¶ _ᶠ_ݶ _Ù¶ _Õ¶ _Ѷ _Ͷ _ɶ _Ŷ _Á¶ _½¶ _¹¶ _µ¶ _±¶ _¶ _©¶ _¥¶ _¡¶ _¶ _™¶ _•¶ _‘¶ _¶ _‰¶ _…¶ _¶ _}¶ _y¶ _u¶ _q¶ _m¶ _i¶ _e¶ _a¶ _]¶ _Y¶ _U¶ _Q¶ _M¶ _I¶ _E¶ _A¶ _=¶ _9¶ _5¶ _1¶ _-¶ _)¶ _%¶ _!¶ _¶ _¶ _¶ _¶ _
¶ _ ¶ _¶ _¶ ^ý¶ ^ù¶ ^õ¶ ^ñ¶ ^í¶ ^é¶ ^å¶ ^ᶠ^ݶ ^Ù¶ ^Õ¶ ^Ѷ ^Ͷ ^ɶ ^Ŷ ^Á¶ ^½¶ ^¹¶ ^µ¶ ^±¶ ^¶ ^©¶ ^¥¶ ^¡¶ ^¶ ^™¶ ^•¶ ^‘¶ ^¶ ^‰¶ ^…¶ ^¶ ^}¶ ^y¶ ^u¶ ^q¶ ^m¶ ^i¶ ^e¶ ^a¶ ^]¶ ^Y¶ ^U¶ ^Q¶ ^M¶ ^I¶ ^E¶ ^A¶ ^=¶ ^9¶ ^5¶ ^1¶ ^-¶ ^)¶ ^%¶ ^!¶ ^¶ ^¶ ^¶ ^¶ ^
¶ ^ ¶ ^¶ ^¶ ]ý¶ ]ù¶ ]õ¶ ]ñ¶ ]í¶ ]é¶ ]å¶ ]ᶠ]ݶ ]Ù¶ ]Õ¶ ]Ѷ ]Ͷ ]ɶ ]Ŷ ]Á¶ ]½¶ ]¹¶ ]µ¶ ]±¶ ]¶ ]©¶ ]¥¶ ]¡¶ ]¶ ]™¶ ]•¶ ]‘¶ ]¶ ]‰¶ ]…¶ ]¶ ]}¶ ]y¶ ]u¶ ]q¶ ]m¶ ]i¶ ]e¶ ]a¶ ]]¶ ]Y¶ ]U¶ ]Q¶ ]M¶ ]I¶ ]E¶ ]A¶ ]=¶ ]9¶ ]5¶ ]1¶ ]-¶ ])¶ ]%¶ ]!¶ ]¶ ]¶ ]¶ ]¶ ]
¶ ] ¶ ]¶ ]¶ \ý¶ \ù¶ \õ¶ \ñ¶ \í¶ \é¶ \å¶ \ᶠ\ݶ \Ù¶ \Õ¶ \Ѷ \Ͷ \ɶ \Ŷ \Á¶ \½¶ \¹¶ \µ¶ \±¶ \¶ \©¶ \¥¶ \¡¶ \¶ \™¶ \•¶ \‘¶ \¶ \‰¶ \…¶ \¶ \}¶ \y¶ \u¶ \q¶ \m¶ \i¶ \e¶ \a¶ \]¶ \Y¶ \U¶ \Q¶ \M¶ \I¶ \E¶ \A¶ \=¶ \9¶ \5¶ \1¶ \-¶ \)¶ \%¶ \!¶ \¶ \¶ \¶ \¶ \
¶ \ ¶ \¶ \¶ [ý¶ [ù¶ [õ¶ [ñ¶ [í¶ [é¶ [å¶ [ᶠ[ݶ [Ù¶ [Õ¶ [Ѷ [Ͷ [ɶ [Ŷ [Á¶ [½¶ [¹¶ [µ¶ [±¶ [¶ [©¶ [¥¶ [¡¶ [¶ [™¶ [•¶ [‘¶ [¶ [‰¶ […¶ [¶ [}¶ [y¶ [u¶ [q¶ [m¶ [i¶ [e¶ [a¶ []¶ [Y¶ [U¶ [Q¶ [M¶ [I¶ [E¶ [A¶ [=¶ [9¶ [5¶ [1¶ [-¶ [)¶ [%¶ [!¶ [¶ [¶ [¶ [¶ [
¶ [ ¶ [¶ [¶ Zý¶ Zù¶ Zõ¶ Zñ¶ Zí¶ Zé¶ Zå¶ ZᶠZݶ ZÙ¶ ZÕ¶ ZѶ ZͶ Zɶ ZŶ ZÁ¶ Z½¶ Z¹¶ Zµ¶ Z±¶ Z¶ Z©¶ Z¥¶ Z¡¶ Z¶ Z™¶ Z•¶ Z‘¶ Z¶ Z‰¶ Z…¶ Z¶ Z}¶ Zy¶ Zu¶ Zq¶ Zm¶ Zi¶ Ze¶ Za¶ Z]¶ ZY¶ ZU¶ ZQ¶ ZM¶ ZI¶ ZE¶ ZA¶ Z=¶ Z9¶ Z5¶ Z1¶ Z-¶ Z)¶ Z%¶ Z!¶ Z¶ Z¶ Z¶ Z¶ Z
¶ Z ¶ Z¶ Z¶ Yý¶ Yù¶ Yõ¶ Yñ¶ Yí¶ Yé¶ Yå¶ YᶠYݶ YÙ¶ YÕ¶ YѶ YͶ Yɶ YŶ YÁ¶ Y½¶ Y¹¶ Yµ¶ Y±¶ Y¶ Y©¶ Y¥¶ Y¡¶ Y¶ Y™¶ Y•¶ Y‘¶ Y¶ Y‰¶ Y…¶ Y¶ Y}¶ Yy¶ Yu¶ Yq¶ Ym¶ Yi¶ Ye¶ Ya¶ Y]¶ YY¶ YU¶ YQ¶ YM¶ YI¶ YE¶ YA¶ Y=¶ Y9¶ Y5¶ Y1¶ Y-¶ Y)¶ Y%¶ Y!¶ Y¶ Y¶ Y¶ Y¶ Y
¶ Y ¶ Y¶ Y¶ Xý¶ Xù¶ Xõ¶ Xñ¶ Xí¶ Xé¶ Xå¶ XᶠXݶ XÙ¶ XÕ¶ XѶ XͶ Xɶ XŶ XÁ¶ X½¶ X¹¶ Xµ¶ X±¶ X¶ X©¶ X¥¶ X¡¶ X¶ X™¶ X•¶ X‘¶ X¶ X‰¶ X…¶ X¶ X}¶ Xy¶ Xu¶ Xq¶ Xm¶ Xi¶ Xe¶ Xa¶ X]¶ XY¶ XU¶ XQ¶ XM¶ XI¶ XE¶ XA¶ X=¶ X9¶ X5¶ X1¶ X-¶ X)¶ X%¶ X!¶ X¶ X¶ X¶ X¶ X
¶ X ¶ X¶ X¶ Wý¶ Wù¶ Wõ¶ Wñ¶ Wí¶ Wé¶ Wå¶ WᶠWݶ WÙ¶ WÕ¶ WѶ WͶ Wɶ WŶ WÁ¶ W½¶ W¹¶ Wµ¶ W±¶ W¶ W©¶ W¥¶ W¡¶ W¶ W™¶ W•¶ W‘¶ W¶ W‰¶ W…¶ W¶ W}¶ Wy¶ Wu¶ Wq¶ Wm¶ Wi¶ We¶ Wa¶ W]¶ WY¶ WU¶ WQ¶ WM¶ WI¶ WE¶ WA¶ W=¶ W9¶ W5¶ W1¶ W-¶ W)¶ W%¶ W!¶ W¶ W¶ W¶ W¶ W
¶ W ¶ W¶ W¶ Vý¶ Vù¶ Võ¶ Vñ¶ Ví¶ Vé¶ Vå¶ VᶠVݶ VÙ¶ VÕ¶ VѶ VͶ Vɶ VŶ VÁ¶ V½¶ V¹¶ Vµ¶ V±¶ V¶ V©¶ V¥¶ V¡¶ V¶ V™¶ V•¶ V‘¶ V¶ V‰¶ V…¶ V¶ V}¶ Vy¶ Vu¶ Vq¶ Vm¶ Vi¶ Ve¶ Va¶ V]¶ VY¶ VU¶ VQ¶ VM¶ VI¶ VE¶ VA¶ V=¶ V9¶ V5¶ V1¶ V-¶ V)¶ V%¶ V!¶ V¶ V¶ V¶ V¶ V
¶ V ¶ V¶ V¶ Uý¶ Uù¶ Uõ¶ Uñ¶ Uí¶ Ué¶ Uå¶ UᶠUݶ UÙ¶ UÕ¶ UѶ UͶ Uɶ UŶ UÁ¶ U½¶ U¹¶ Uµ¶ U±¶ U¶ U©¶ U¥¶ U¡¶ U¶ U™¶ U•¶ U‘¶ U¶ U‰¶ U…¶ U¶ U}¶ Uy¶ Uu¶ Uq¶ Um¶ Ui¶ Ue¶ Ua¶ U]¶ UY¶ UU¶ UQ¶ UM¶ UI¶ UE¶ UA¶ U=¶ U9¶ U5¶ U1¶ U-¶ U)¶ U%¶ U!¶ U¶ U¶ U¶ U¶ U
¶ U ¶ U¶ U¶ Tý¶ Tù¶ Tõ¶ Tñ¶ Tí¶ Té¶ Tå¶ TᶠTݶ TÙ¶ TÕ¶ TѶ TͶ Tɶ TŶ TÁ¶ T½¶ T¹¶ Tµ¶ T±¶ T¶ T©¶ T¥¶ T¡¶ T¶ T™¶ T•¶ T‘¶ T¶ T‰¶ T…¶ T¶ T}¶ Ty¶ Tu¶ Tq¶ Tm¶ Ti¶ Te¶ Ta¶ T]¶ TY¶ TU¶ TQ¶ TM¶ TI¶ TE¶ TA¶ T=¶ T9¶ T5¶ T1¶ T-¶ T)¶ T%¶ T!¶ T¶ T¶ T¶ T¶ T
¶ T ¶ T¶ T¶ Sý¶ Sù¶ Sõ¶ Sñ¶ Sí¶ Sé¶ Så¶ SᶠSݶ SÙ¶ SÕ¶ SѶ SͶ Sɶ SŶ SÁ¶ S½¶ S¹¶ Sµ¶ S±¶ S¶ S©¶ S¥¶ S¡¶ S¶ S™¶ S•¶ S‘¶ S¶ S‰¶ S…¶ S¶ S}¶ Sy¶ Su¶ Sq¶ Sm¶ Si¶ Se¶ Sa¶ S]¶ SY¶ SU¶ SQ¶ SM¶ SI¶ SE¶ SA¶ S=¶ S9¶ S5¶ S1¶ S-¶ S)¶ S%¶ S!¶ S¶ S¶ S¶ S¶ S
¶ S ¶ S¶ S¶ Rý¶ Rù¶ Rõ¶ Rñ¶ Rí¶ Ré¶ Rå¶ RᶠRݶ RÙ¶ RÕ¶ RѶ RͶ Rɶ RŶ RÁ¶ R½¶ R¹¶ Rµ¶ R±¶ R¶ R©¶ R¥¶ R¡¶ R¶ R™¶ R•¶ R‘¶ R¶ R‰¶ R…¶ R¶ R}¶ Ry¶ Ru¶ Rq¶ Rm¶ Ri¶ Re¶ Ra¶ R]¶ RY¶ RU¶ RQ¶ RM¶ RI¶ RE¶ RA¶ R=¶ R9¶ R5¶ R1¶ R-¶ R)¶ R%¶ R!¶ R¶ R¶ R¶ R¶ R
¶ R ¶ R¶ R¶ Qý¶ Qù¶ Qõ¶ Qñ¶ Qí¶ Qé¶ Qå¶ QᶠQݶ QÙ¶ QÕ¶ QѶ QͶ Qɶ QŶ QÁ¶ Q½¶ Q¹¶ Qµ¶ Q±¶ Q¶ Q©¶ Q¥¶ Q¡¶ Q¶ Q™¶ Q•¶ Q‘¶ Q¶ Q‰¶ Q…¶ Q¶ Q}¶ Qy¶ Qu¶ Qq¶ Qm¶ Qi¶ Qe¶ Qa¶ Q]¶ QY¶ QU¶ QQ¶ QM¶ QI¶ QE¶ QA¶ Q=¶ Q9¶ Q5¶ Q1¶ Q-¶ Q)¶ Q%¶ Q!¶ Q¶ Q¶ Q¶ Q¶ Q
¶ Q ¶ Q¶ Q¶ Pý¶ Pù¶ Põ¶ Pñ¶ Pí¶ Pé¶ På¶ PᶠPݶ PÙ¶ PÕ¶ PѶ PͶ Pɶ PŶ PÁ¶ P½¶ P¹¶ Pµ¶ P±¶ P¶ P©¶ P¥¶ P¡¶ P¶ P™¶ P•¶ P‘¶ P¶ P‰¶ P…¶ P¶ P}¶ Py¶ Pu¶ Pq¶ Pm¶ Pi¶ Pe¶ Pa¶ P]¶ PY¶ PU¶ PQ¶ PM¶ PI¶ PE¶ PA¶ P=¶ P9¶ P5¶ P1¶ P-¶ P)¶ P%¶ P!¶ P¶ P¶ P¶ P¶ P
¶ P ¶ P¶ P¶ Oý¶ Où¶ Oõ¶ Oñ¶ Oí¶ Oé¶ Oå¶ OᶠOݶ OÙ¶ OÕ¶ OѶ OͶ Oɶ OŶ OÁ¶ O½¶ O¹¶ Oµ¶ O±¶ O¶ O©¶ O¥¶ O¡¶ O¶ O™¶ O•¶ O‘¶ O¶ O‰¶ O…¶ O¶ O}¶ Oy¶ Ou¶ Oq¶ Om¶ Oi¶ Oe¶ Oa¶ O]¶ OY¶ OU¶ OQ¶ OM¶ OI¶ OE¶ OA¶ O=¶ O9¶ O5¶ O1¶ O-¶ O)¶ O%¶ O!¶ O¶ O¶ O¶ O¶ O
¶ O ¶ O¶ O¶ Ný¶ Nù¶ Nõ¶ Nñ¶ Ní¶ Né¶ Nå¶ NᶠNݶ NÙ¶ NÕ¶ NѶ NͶ Nɶ NŶ NÁ¶ N½¶ N¹¶ Nµ¶ N±¶ N¶ N©¶ N¥¶ N¡¶ N¶ N™¶ N•¶ N‘¶ N¶ N‰¶ N…¶ N¶ N}¶ Ny¶ Nu¶ Nq¶ Nm¶ Ni¶ Ne¶ Na¶ N]¶ NY¶ NU¶ NQ¶ NM¶ NI¶ NE¶ NA¶ N=¶ N9¶ N5¶ N1¶ N-¶ N)¶ N%¶ N!¶ N¶ N¶ N¶ N¶ N
¶ N ¶ N¶ N¶ Mý¶ Mù¶ Mõ¶ Mñ¶ Mí¶ Mé¶ Må¶ MᶠMݶ MÙ¶ MÕ¶ MѶ MͶ Mɶ MŶ MÁ¶ M½¶ M¹¶ Mµ¶ M±¶ M¶ M©¶ M¥¶ M¡¶ M¶ M™¶ M•¶ M‘¶ M¶ M‰¶ M…¶ M¶ M}¶ My¶ Mu¶ Mq¶ Mm¶ Mi¶ Me¶ Ma¶ M]¶ MY¶ MU¶ MQ¶ MM¶ MI¶ ME¶ MA¶ M=¶ M9¶ M5¶ M1¶ M-¶ M)¶ M%¶ M!¶ M¶ M¶ M¶ M¶ M
¶ M ¶ M¶ M¶ Lý¶ Lù¶ Lõ¶ Lñ¶ Lí¶ Lé¶ Lå¶ LᶠLݶ LÙ¶ LÕ¶ LѶ LͶ Lɶ LŶ LÁ¶ L½¶ L¹¶ Lµ¶ L±¶ L¶ L©¶ L¥¶ L¡¶ L¶ L™¶ L•¶ L‘¶ L¶ L‰¶ L…¶ L¶ L}¶ Ly¶ Lu¶ Lq¶ Lm¶ Li¶ Le¶ La¶ L]¶ LY¶ LU¶ LQ¶ LM¶ LI¶ LE¶ LA¶ L=¶ L9¶ L5¶ L1¶ L-¶ L)¶ L%¶ L!¶ L¶ L¶ L¶ L¶ L
¶ L ¶ L¶ L¶ Ký¶ Kù¶ Kõ¶ Kñ¶ Kí¶ Ké¶ Kå¶ KᶠKݶ KÙ¶ KÕ¶ KѶ KͶ Kɶ KŶ KÁ¶ K½¶ K¹¶ Kµ¶ K±¶ K¶ K©¶ K¥¶ K¡¶ K¶ K™¶ K•¶ K‘¶ K¶ K‰¶ K…¶ K¶ K}¶ Ky¶ Ku¶ Kq¶ Km¶ Ki¶ Ke¶ Ka¶ K]¶ KY¶ KU¶ KQ¶ KM¶ KI¶ KE¶ KA¶ K=¶ K9¶ K5¶ K1¶ K-¶ K)¶ K%¶ K!¶ K¶ K¶ K¶ K¶ K
¶ K ¶ K¶ K¶ Jý¶ Jù¶ Jõ¶ Jñ¶ Jí¶ Jé¶ Jå¶ JᶠJݶ JÙ¶ JÕ¶ JѶ JͶ Jɶ JŶ JÁ¶ J½¶ J¹¶ Jµ¶ J±¶ J¶ J©¶ J¥¶ J¡¶ J¶ J™¶ J•¶ J‘¶ J¶ J‰¶ J…¶ J¶ J}¶ Jy¶ Ju¶ Jq¶ Jm¶ Ji¶ Je¶ Ja¶ J]¶ JY¶ JU¶ JQ¶ JM¶ JI¶ JE¶ JA¶ J=¶ J9¶ J5¶ J1¶ J-¶ J)¶ J%¶ J!¶ J¶ J¶ J¶ J¶ J
¶ J ¶ J¶ J¶ Iý¶ Iù¶ Iõ¶ Iñ¶ Ií¶ Ié¶ Iå¶ IᶠIݶ IÙ¶ IÕ¶ IѶ IͶ Iɶ IŶ IÁ¶ I½¶ I¹¶ Iµ¶ I±¶ I¶ I©¶ I¥¶ I¡¶ I¶ I™¶ I•¶ I‘¶ I¶ I‰¶ I…¶ I¶ I}¶ Iy¶ Iu¶ Iq¶ Im¶ Ii¶ Ie¶ Ia¶ I]¶ IY¶ IU¶ IQ¶ IM¶ II¶ IE¶ IA¶ I=¶ I9¶ I5¶ I1¶ I-¶ I)¶ I%¶ I!¶ I¶ I¶ I¶ I¶ I
¶ I ¶ I¶ I¶ Hý¶ Hù¶ Hõ¶ Hñ¶ Hí¶ Hé¶ Hå¶ HᶠHݶ HÙ¶ HÕ¶ HѶ HͶ Hɶ HŶ HÁ¶ H½¶ H¹¶ Hµ¶ H±¶ H¶ H©¶ H¥¶ H¡¶ H¶ H™¶ H•¶ H‘¶ H¶ H‰¶ H…¶ H¶ H}¶ Hy¶ Hu¶ Hq¶ Hm¶ Hi¶ He¶ Ha¶ H]¶ HY¶ HU¶ HQ¶ HM¶ HI¶ HE¶ HA¶ H=¶ H9¶ H5¶ H1¶ H-¶ H)¶ H%¶ H!¶ H¶ H¶ H¶ H¶ H
¶ H ¶ H¶ H¶ Gý¶ Gù¶ Gõ¶ Gñ¶ Gí¶ Gé¶ Gå¶ GᶠGݶ GÙ¶ GÕ¶ GѶ GͶ Gɶ GŶ GÁ¶ G½¶ G¹¶ Gµ¶ G±¶ G¶ G©¶ G¥¶ G¡¶ G¶ G™¶ G•¶ G‘¶ G¶ G‰¶ G…¶ G¶ G}¶ Gy¶ Gu¶ Gq¶ Gm¶ Gi¶ Ge¶ Ga¶ G]¶ GY¶ GU¶ GQ¶ GM¶ GI¶ GE¶ GA¶ G=¶ G9¶ G5¶ G1¶ G-¶ G)¶ G%¶ G!¶ G¶ G¶ G¶ G¶ G
¶ G ¶ G¶ G¶ Fý¶ Fù¶ Fõ¶ Fñ¶ Fí¶ Fé¶ Få¶ FᶠFݶ FÙ¶ FÕ¶ FѶ FͶ Fɶ FŶ FÁ¶ F½¶ F¹¶ Fµ¶ F±¶ F¶ F©¶ F¥¶ F¡¶ F¶ F™¶ F•¶ F‘¶ F¶ F‰¶ F…¶ F¶ F}¶ Fy¶ Fu¶ Fq¶ Fm¶ Fi¶ Fe¶ Fa¶ F]¶ FY¶ FU¶ FQ¶ FM¶ FI¶ FE¶ FA¶ F=¶ F9¶ F5¶ F1¶ F-¶ F)¶ F%¶ F!¶ F¶ F¶ F¶ F¶ F
¶ F ¶ F¶ F¶ Eý¶ Eù¶ Eõ¶ Eñ¶ Eí¶ Eé¶ Eå¶ EᶠEݶ EÙ¶ EÕ¶ EѶ EͶ Eɶ EŶ EÁ¶ E½¶ E¹¶ Eµ¶ E±¶ E¶ E©¶ E¥¶ E¡¶ E¶ E™¶ E•¶ E‘¶ E¶ E‰¶ E…¶ E¶ E}¶ Ey¶ Eu¶ Eq¶ Em¶ Ei¶ Ee¶ Ea¶ E]¶ EY¶ EU¶ EQ¶ EM¶ EI¶ EE¶ EA¶ E=¶ E9¶ E5¶ E1¶ E-¶ E)¶ E%¶ E!¶ E¶ E¶ E¶ E¶ E
¶ E ¶ E¶ E¶ Dý¶ Dù¶ Dõ¶ Dñ¶ Dí¶ Dé¶ Då¶ DᶠDݶ DÙ¶ DÕ¶ DѶ DͶ Dɶ DŶ DÁ¶ D½¶ D¹¶ Dµ¶ D±¶ D¶ D©¶ D¥¶ D¡¶ D¶ D™¶ D•¶ D‘¶ D¶ D‰¶ D…¶ D¶ D}¶ Dy¶ Du¶ Dq¶ Dm¶ Di¶ De¶ Da¶ D]¶ DY¶ DU¶ DQ¶ DM¶ DI¶ DE¶ DA¶ D=¶ D9¶ D5¶ D1¶ D-¶ D)¶ D%¶ D!¶ D¶ D¶ D¶ D¶ D
¶ D ¶ D¶ D¶ Cý¶ Cù¶ Cõ¶ Cñ¶ Cí¶ Cé¶ Cå¶ CᶠCݶ CÙ¶ CÕ¶ CѶ CͶ Cɶ CŶ CÁ¶ C½¶ C¹¶ Cµ¶ C±¶ C¶ C©¶ C¥¶ C¡¶ C¶ C™¶ C•¶ C‘¶ C¶ C‰¶ C…¶ C¶ C}¶ Cy¶ Cu¶ Cq¶ Cm¶ Ci¶ Ce¶ Ca¶ C]¶ CY¶ CU¶ CQ¶ CM¶ CI¶ CE¶ CA¶ C=¶ C9¶ C5¶ C1¶ C-¶ C)¶ C%¶ C!¶ C¶ C¶ C¶ C¶ C
¶ C ¶ C¶ C¶ Bý¶ Bù¶ Bõ¶ Bñ¶ Bí¶ Bé¶ Bå¶ BᶠBݶ BÙ¶ BÕ¶ BѶ BͶ Bɶ BŶ BÁ¶ B½¶ B¹¶ Bµ¶ B±¶ B¶ B©¶ B¥¶ B¡¶ B¶ B™¶ B•¶ B‘¶ B¶ B‰¶ B…¶ B¶ B}¶ By¶ Bu¶ Bq¶ Bm¶ Bi¶ Be¶ Ba¶ B]¶ BY¶ BU¶ BQ¶ BM¶ BI¶ BE¶ BA¶ B=¶ B9¶ B5¶ B1¶ B-¶ B)¶ B%¶ B!¶ B¶ B¶ B¶ B¶ B
¶ B ¶ B¶ B¶ Aý¶ Aù¶ Aõ¶ Añ¶ Aí¶ Aé¶ Aå¶ AᶠAݶ AÙ¶ AÕ¶ AѶ AͶ Aɶ AŶ AÁ¶ A½¶ A¹¶ Aµ¶ A±¶ A¶ A©¶ A¥¶ A¡¶ A¶ A™¶ A•¶ A‘¶ A¶ A‰¶ A…¶ A¶ A}¶ Ay¶ Au¶ Aq¶ Am¶ Ai¶ Ae¶ Aa¶ A]¶ AY¶ AU¶ AQ¶ AM¶ AI¶ AE¶ AA¶ A=¶ A9¶ A5¶ A1¶ A-¶ A)¶ A%¶ A!¶ A¶ A¶ A¶ A¶ A
¶ A ¶ A¶ A¶ @ý¶ @ù¶ @õ¶ @ñ¶ @í¶ @é¶ @å¶ @ᶠ@ݶ @Ù¶ @Õ¶ @Ѷ @Ͷ @ɶ @Ŷ @Á¶ @½¶ @¹¶ @µ¶ @±¶ @¶ @©¶ @¥¶ @¡¶ @¶ @™¶ @•¶ @‘¶ @¶ @‰¶ @…¶ @¶ @}¶ @y¶ @u¶ @q¶ @m¶ @i¶ @e¶ @a¶ @]¶ @Y¶ @U¶ @Q¶ @M¶ @I¶ @E¶ @A¶ @=¶ @9¶ @5¶ @1¶ @-¶ @)¶ @%¶ @!¶ @¶ @¶ @¶ @¶ @
¶ @ ¶ @¶ @¶þ¶û¶ø¶õ¶ò¶ï¶ì¶é¶æ¶ã¶à¶Ý¶Ú¶×¶Ô¶Ñ¶Î¶Ë¶È¶Å¶Â¶¿¶¼¶¹¶¶¶³¶°¶¶ª¶§¶¤¶¡¶ž¶›¶˜¶•¶’¶¶Œ¶‰¶†¶ƒ¶€¶}¶z¶w¶t¶q¶n¶k¶h¶e¶b¶_¶\¶Y¶V¶S¶P¶M¶J¶G¶D¶A¶>¶;¶8¶5¶2¶/¶,¶)¶&¶#¶ ¶¶¶¶¶¶¶¶¶¶¶~ÿ¶~ü¶~ù¶~ö¶~ó¶~ð¶~í¶~ê¶~ç¶~ä¶~á¶~Þ¶~Û¶~ض~Õ¶~Ò¶~϶~̶~ɶ~ƶ~ö~À¶~½¶~º¶~·¶~´¶~±¶~®¶~«¶~¨¶~¥¶~¢¶~Ÿ¶~œ¶~™¶~–¶~“¶~¶~¶~Š¶~‡¶~„¶~¶~~¶~{¶~x¶~u¶~r¶~o¶~l¶~i¶~f¶~c¶~`¶~]¶~Z¶~W¶~T¶~Q¶~N¶~K¶~H¶~E¶~B¶~?¶~<¶~9¶~6¶~3¶~0¶~-¶~*¶~'¶~$¶~!¶~¶~¶~¶~¶~¶~¶~¶~ ¶~¶~¶~ +¶}¶}¶}¶|þ¶|û¶|ø¶|õ¶|ò¶|ï¶|ì¶|é¶|æ¶|ã¶|à¶|ݶ|Ú¶|׶|Ô¶|Ѷ|ζ|˶|ȶ|Ŷ|¶|¿¶|¼¶|¹¶|¶¶|³¶|°¶|¶|ª¶|§¶|¤¶|¡¶|ž¶|›¶|˜¶|•¶|’¶|¶|Œ¶|‰¶|†¶|ƒ¶|€¶|}¶|z¶|w¶|t¶|q¶|n¶|k¶|h¶|e¶|b¶|_¶|\¶|Y¶|V¶|S¶|P¶|M¶|J¶|G¶|D¶|A¶|>¶|;¶|8¶|5¶|2¶|/¶|,¶|)¶|&¶|#¶| ¶|¶|¶|¶|¶|¶|¶|¶|¶|¶|¶{ÿ¶{ü¶{ù¶{ö¶{ó¶{ð¶{í¶{ê¶{ç¶{ä¶{á¶{Þ¶{Û¶{ض{Õ¶{Ò¶{϶{̶{ɶ{ƶ{ö{À¶{½¶{º¶{·¶{´¶{±¶{®¶{«¶{¨¶{¥¶{¢¶{Ÿ¶{œ¶{™¶{–¶{“¶{¶{¶{Š¶{‡¶{„¶{¶{~¶{{¶{x¶{u¶{r¶{o¶{l¶{i¶{f¶{c¶{`¶{]¶{Z¶{W¶{T¶{Q¶{N¶{K¶{H¶{E¶{B¶{?¶{<¶{9¶{6¶{3¶{0¶{-¶{*¶{'¶{$¶{!¶{¶{¶{¶{¶{¶{¶{¶{ ¶{¶{¶{ +¶z¶z¶z¶yþ¶yû¶yø¶yõ¶yò¶yï¶yì¶yé¶yæ¶yã¶yà¶yݶyÚ¶y׶yÔ¶yѶyζy˶yȶyŶy¶y¿¶y¼¶y¹¶y¶¶y³¶y°¶y¶yª¶y§¶y¤¶y¡¶yž¶y›¶y˜¶y•¶y’¶y¶yŒ¶y‰¶y†¶yƒ¶y€¶y}¶yz¶yw¶yt¶yq¶yn¶yk¶yh¶ye¶yb¶y_¶y\¶yY¶yV¶yS¶yP¶yM¶yJ¶yG¶yD¶yA¶y>¶y;¶y8¶y5¶y2¶y/¶y,¶y)¶y&¶y#¶y ¶y¶y¶y¶y¶y¶y¶y¶y¶y¶y¶xÿ¶xü¶xù¶xö¶xó¶xð¶xí¶xê¶xç¶xä¶xá¶xÞ¶xÛ¶xضxÕ¶xÒ¶x϶x̶xɶxƶxöxÀ¶x½¶xº¶x·¶x´¶x±¶x®¶x«¶x¨¶x¥¶x¢¶xŸ¶xœ¶x™¶x–¶x“¶x¶x¶xŠ¶x‡¶x„¶x¶x~¶x{¶xx¶xu¶xr¶xo¶xl¶xi¶xf¶xc¶x`¶x]¶xZ¶xW¶xT¶xQ¶xN¶xK¶xH¶xE¶xB¶x?¶x<¶x9¶x6¶x3¶x0¶x-¶x*¶x'¶x$¶x!¶x¶x¶x¶x¶x¶x¶x¶x ¶x¶x¶x +¶w¶w¶w¶vþ¶vû¶vø¶võ¶vò¶vï¶vì¶vé¶væ¶vã¶và¶vݶvÚ¶v׶vÔ¶vѶvζv˶vȶvŶv¶v¿¶v¼¶v¹¶v¶¶v³¶v°¶v¶vª¶v§¶v¤¶v¡¶vž¶v›¶v˜¶v•¶v’¶v¶vŒ¶v‰¶v†¶vƒ¶v€¶v}¶vz¶vw¶vt¶vq¶vn¶vk¶vh¶ve¶vb¶v_¶v\¶vY¶vV¶vS¶vP¶vM¶vJ¶vG¶vD¶vA¶v>¶v;¶v8¶v5¶v2¶v/¶v,¶v)¶v&¶v#¶v ¶v¶v¶v¶v¶v¶v¶v¶v¶v¶v¶uÿ¶uü¶uù¶uö¶uó¶uð¶uí¶uê¶uç¶uä¶uá¶uÞ¶uÛ¶uضuÕ¶uÒ¶u϶u̶uɶuƶuöuÀ¶u½¶uº¶u·¶u´¶u±¶u®¶u«¶u¨¶u¥¶u¢¶uŸ¶uœ¶u™¶u–¶u“¶u¶u¶uŠ¶u‡¶u„¶u¶u~¶u{¶ux¶uu¶ur¶uo¶ul¶ui¶uf¶uc¶u`¶u]¶uZ¶uW¶uT¶uQ¶uN¶uK¶uH¶uE¶uB¶u?¶u<¶u9¶u6¶u3¶u0¶u-¶u*¶u'¶u$¶u!¶u¶u¶u¶u¶u¶u¶u¶u ¶u¶u¶u +¶t¶t¶t¶sþ¶sû¶sø¶sõ¶sò¶sï¶sì¶sé¶sæ¶sã¶sà¶sݶsÚ¶s׶sÔ¶sѶsζs˶sȶsŶs¶s¿¶s¼¶s¹¶s¶¶s³¶s°¶s¶sª¶s§¶s¤¶s¡¶sž¶s›¶s˜¶s•¶s’¶s¶sŒ¶s‰¶s†¶sƒ¶s€¶s}¶sz¶sw¶st¶sq¶sn¶sk¶sh¶se¶sb¶s_¶s\¶sY¶sV¶sS¶sP¶sM¶sJ¶sG¶sD¶sA¶s>¶s;¶s8¶s5¶s2¶s/¶s,¶s)¶s&¶s#¶s ¶s¶s¶s¶s¶s¶s¶s¶s¶s¶s¶rÿ¶rü¶rù¶rö¶ró¶rð¶rí¶rê¶rç¶rä¶rá¶rÞ¶rÛ¶rضrÕ¶rÒ¶r϶r̶rɶrƶrörÀ¶r½¶rº¶r·¶r´¶r±¶r®¶r«¶r¨¶r¥¶r¢¶rŸ¶rœ¶r™¶r–¶r“¶r¶r¶rŠ¶r‡¶r„¶r¶r~¶r{¶rx¶ru¶rr¶ro¶rl¶ri¶rf¶rc¶r`¶r]¶rZ¶rW¶rT¶rQ¶rN¶rK¶rH¶rE¶rB¶r?¶r<¶r9¶r6¶r3¶r0¶r-¶r*¶r'¶r$¶r!¶r¶r¶r¶r¶r¶r¶r¶r ¶r¶r¶r +¶q¶q¶q¶pþ¶pû¶pø¶põ¶pò¶pï¶pì¶pé¶pæ¶pã¶pà¶pݶpÚ¶p׶pÔ¶pѶpζp˶pȶpŶp¶p¿¶p¼¶p¹¶p¶¶p³¶p°¶p¶pª¶p§¶p¤¶p¡¶pž¶p›¶p˜¶p•¶p’¶p¶pŒ¶p‰¶p†¶pƒ¶p€¶p}¶pz¶pw¶pt¶pq¶pn¶pk¶ph¶pe¶pb¶p_¶p\¶pY¶pV¶pS¶pP¶pM¶pJ¶pG¶pD¶pA¶p>¶p;¶p8¶p5¶p2¶p/¶p,¶p)¶p&¶p#¶p ¶p¶p¶p¶p¶p¶p¶p¶p¶p¶p¶oÿ¶oü¶où¶oö¶oó¶oð¶oí¶oê¶oç¶oä¶oá¶oÞ¶oÛ¶oضoÕ¶oÒ¶o϶o̶oɶoƶoöoÀ¶o½¶oº¶o·¶o´¶o±¶o®¶o«¶o¨¶o¥¶o¢¶oŸ¶oœ¶o™¶o–¶o“¶o¶o¶oŠ¶o‡¶o„¶o¶o~¶o{¶ox¶ou¶or¶oo¶ol¶oi¶of¶oc¶o`¶o]¶oZ¶oW¶oT¶oQ¶oN¶oK¶oH¶oE¶oB¶o?¶o<¶o9¶o6¶o3¶o0¶o-¶o*¶o'¶o$¶o!¶o¶o¶o¶o¶o¶o¶o¶o ¶o¶o¶o +¶n¶n¶n¶mþ¶mû¶mø¶mõ¶mò¶mï¶mì¶mé¶mæ¶mã¶mà¶mݶmÚ¶m׶mÔ¶mѶmζm˶mȶmŶm¶m¿¶m¼¶m¹¶m¶¶m³¶m°¶m¶mª¶m§¶m¤¶m¡¶mž¶m›¶m˜¶m•¶m’¶m¶mŒ¶m‰¶m†¶mƒ¶m€¶m}¶mz¶mw¶mt¶mq¶mn¶mk¶mh¶me¶mb¶m_¶m\¶mY¶mV¶mS¶mP¶mM¶mJ¶mG¶mD¶mA¶m>¶m;¶m8¶m5¶m2¶m/¶m,¶m)¶m&¶m#¶m ¶m¶m¶m¶m¶m¶m¶m¶m¶m¶m¶lÿ¶lü¶lù¶lö¶ló¶lð¶lí¶lê¶lç¶lä¶lá¶lÞ¶lÛ¶lضlÕ¶lÒ¶l϶l̶lɶlƶlölÀ¶l½¶lº¶l·¶l´¶l±¶l®¶l«¶l¨¶l¥¶l¢¶lŸ¶lœ¶l™¶l–¶l“¶l¶l¶lŠ¶l‡¶l„¶l¶l~¶l{¶lx¶lu¶lr¶lo¶ll¶li¶lf¶lc¶l`¶l]¶lZ¶lW¶lT¶lQ¶lN¶lK¶lH¶lE¶lB¶l?¶l<¶l9¶l6¶l3¶l0¶l-¶l*¶l'¶l$¶l!¶l¶l¶l¶l¶l¶l¶l¶l ¶l¶l¶l +¶k¶k¶k¶jþ¶jû¶jø¶jõ¶jò¶jï¶jì¶jé¶jæ¶jã¶jà¶jݶjÚ¶j׶jÔ¶jѶjζj˶jȶjŶj¶j¿¶j¼¶j¹¶j¶¶j³¶j°¶j¶jª¶j§¶j¤¶j¡¶jž¶j›¶j˜¶j•¶j’¶j¶jŒ¶j‰¶j†¶jƒ¶j€¶j}¶jz¶jw¶jt¶jq¶jn¶jk¶jh¶je¶jb¶j_¶j\¶jY¶jV¶jS¶jP¶jM¶jJ¶jG¶jD¶jA¶j>¶j;¶j8¶j5¶j2¶j/¶j,¶j)¶j&¶j#¶j ¶j¶j¶j¶j¶j¶j¶j¶j¶j¶j¶iÿ¶iü¶iù¶iö¶ió¶ið¶ií¶iê¶iç¶iä¶iá¶iÞ¶iÛ¶iضiÕ¶iÒ¶i϶i̶iɶiƶiöiÀ¶i½¶iº¶i·¶i´¶i±¶i®¶i«¶i¨¶i¥¶i¢¶iŸ¶iœ¶i™¶i–¶i“¶i¶i¶iŠ¶i‡¶i„¶i¶i~¶i{¶ix¶iu¶ir¶io¶il¶ii¶if¶ic¶i`¶i]¶iZ¶iW¶iT¶iQ¶iN¶iK¶iH¶iE¶iB¶i?¶i<¶i9¶i6¶i3¶i0¶i-¶i*¶i'¶i$¶i!¶i¶i¶i¶i¶i¶i¶i¶i ¶i¶i¶i +¶h¶h¶h¶gþ¶gû¶gø¶gõ¶gò¶gï¶gì¶gé¶gæ¶gã¶gà¶gݶgÚ¶g׶gÔ¶gѶgζg˶gȶgŶg¶g¿¶g¼¶g¹¶g¶¶g³¶g°¶g¶gª¶g§¶g¤¶g¡¶gž¶g›¶g˜¶g•¶g’¶g¶gŒ¶g‰¶g†¶gƒ¶g€¶g}¶gz¶gw¶gt¶gq¶gn¶gk¶gh¶ge¶gb¶g_¶g\¶gY¶gV¶gS¶gP¶gM¶gJ¶gG¶gD¶gA¶g>¶g;¶g8¶g5¶g2¶g/¶g,¶g)¶g&¶g#¶g ¶g¶g¶g¶g¶g¶g¶g¶g¶g¶g¶fÿ¶fü¶fù¶fö¶fó¶fð¶fí¶fê¶fç¶fä¶fá¶fÞ¶fÛ¶fضfÕ¶fÒ¶f϶f̶fɶfƶföfÀ¶f½¶fº¶f·¶f´¶f±¶f®¶f«¶f¨¶f¥¶f¢¶fŸ¶fœ¶f™¶f–¶f“¶f¶f¶fŠ¶f‡¶f„¶f¶f~¶f{¶fx¶fu¶fr¶fo¶fl¶fi¶ff¶fc¶f`¶f]¶fZ¶fW¶fT¶fQ¶fN¶fK¶fH¶fE¶fB¶f?¶f<¶f9¶f6¶f3¶f0¶f-¶f*¶f'¶f$¶f!¶f¶f¶f¶f¶f¶f¶f¶f ¶f¶f¶f +¶e¶e¶e¶dþ¶dû¶dø¶dõ¶dò¶dï¶dì¶dé¶dæ¶dã¶dà¶dݶdÚ¶d׶dÔ¶dѶdζd˶dȶdŶd¶d¿¶d¼¶d¹¶d¶¶d³¶d°¶d¶dª¶d§¶d¤¶d¡¶dž¶d›¶d˜¶d•¶d’¶d¶dŒ¶d‰¶d†¶dƒ¶d€¶d}¶dz¶dw¶dt¶dq¶dn¶dk¶dh¶de¶db¶d_¶d\¶dY¶dV¶dS¶dP¶dM¶dJ¶dG¶dD¶dA¶d>¶d;¶d8¶d5¶d2¶d/¶d,¶d)¶d&¶d#¶d ¶d¶d¶d¶d¶d¶d¶d¶d¶d¶d¶cÿ¶cü¶cù¶cö¶có¶cð¶cí¶cê¶cç¶cä¶cá¶cÞ¶cÛ¶cضcÕ¶cÒ¶c϶c̶cɶcƶcöcÀ¶c½¶cº¶c·¶c´¶c±¶c®¶c«¶c¨¶c¥¶c¢¶cŸ¶cœ¶c™¶c–¶c“¶c¶c¶cŠ¶c‡¶c„¶c¶c~¶c{¶cx¶cu¶cr¶co¶cl¶ci¶cf¶cc¶c`¶c]¶cZ¶cW¶cT¶cQ¶cN¶cK¶cH¶cE¶cB¶c?¶c<¶c9¶c6¶c3¶c0¶c-¶c*¶c'¶c$¶c!¶c¶c¶c¶c¶c¶c¶c¶c ¶c¶c¶c +¶b¶b¶b¶aþ¶aû¶aø¶aõ¶aò¶aï¶aì¶aé¶aæ¶aã¶aà¶aݶaÚ¶a׶aÔ¶aѶaζa˶aȶaŶa¶a¿¶a¼¶a¹¶a¶¶a³¶a°¶a¶aª¶a§¶a¤¶a¡¶až¶a›¶a˜¶a•¶a’¶a¶aŒ¶a‰¶a†¶aƒ¶a€¶a}¶az¶aw¶at¶aq¶an¶ak¶ah¶ae¶ab¶a_¶a\¶aY¶aV¶aS¶aP¶aM¶aJ¶aG¶aD¶aA¶a>¶a;¶a8¶a5¶a2¶a/¶a,¶a)¶a&¶a#¶a ¶a¶a¶a¶a¶a¶a¶a¶a¶a¶a¶`ÿ¶`ü¶`ù¶`ö¶`ó¶`ð¶`í¶`ê¶`ç¶`ä¶`á¶`Þ¶`Û¶`ض`Õ¶`Ò¶`϶`̶`ɶ`ƶ`ö`À¶`½¶`º¶`·¶`´¶`±¶`®¶`«¶`¨¶`¥¶`¢¶`Ÿ¶`œ¶`™¶`–¶`“¶`¶`¶`Š¶`‡¶`„¶`¶`~¶`{¶`x¶`u¶`r¶`o¶`l¶`i¶`f¶`c¶``¶`]¶`Z¶`W¶`T¶`Q¶`N¶`K¶`H¶`E¶`B¶`?¶`<¶`9¶`6¶`3¶`0¶`-¶`*¶`'¶`$¶`!¶`¶`¶`¶`¶`¶`¶`¶` ¶`¶`¶` +¶_¶_¶_¶^þ¶^û¶^ø¶^õ¶^ò¶^ï¶^ì¶^é¶^æ¶^ã¶^à¶^ݶ^Ú¶^׶^Ô¶^Ѷ^ζ^˶^ȶ^Ŷ^¶^¿¶^¼¶^¹¶^¶¶^³¶^°¶^¶^ª¶^§¶^¤¶^¡¶^ž¶^›¶^˜¶^•¶^’¶^¶^Œ¶^‰¶^†¶^ƒ¶^€¶^}¶^z¶^w¶^t¶^q¶^n¶^k¶^h¶^e¶^b¶^_¶^\¶^Y¶^V¶^S¶^P¶^M¶^J¶^G¶^D¶^A¶^>¶^;¶^8¶^5¶^2¶^/¶^,¶^)¶^&¶^#¶^ ¶^¶^¶^¶^¶^¶^¶^¶^¶^¶^¶]ÿ¶]ü¶]ù¶]ö¶]ó¶]ð¶]í¶]ê¶]ç¶]ä¶]á¶]Þ¶]Û¶]ض]Õ¶]Ò¶]϶]̶]ɶ]ƶ]ö]À¶]½¶]º¶]·¶]´¶]±¶]®¶]«¶]¨¶]¥¶]¢¶]Ÿ¶]œ¶]™¶]–¶]“¶]¶]¶]Š¶]‡¶]„¶]¶]~¶]{¶]x¶]u¶]r¶]o¶]l¶]i¶]f¶]c¶]`¶]]¶]Z¶]W¶]T¶]Q¶]N¶]K¶]H¶]E¶]B¶]?¶]<¶]9¶]6¶]3¶]0¶]-¶]*¶]'¶]$¶]!¶]¶]¶]¶]¶]¶]¶]¶] ¶]¶]¶] +¶\¶\¶\¶[þ¶[û¶[ø¶[õ¶[ò¶[ï¶[ì¶[é¶[æ¶[ã¶[à¶[ݶ[Ú¶[׶[Ô¶[Ѷ[ζ[˶[ȶ[Ŷ[¶[¿¶[¼¶[¹¶[¶¶[³¶[°¶[¶[ª¶[§¶[¤¶[¡¶[ž¶[›¶[˜¶[•¶[’¶[¶[Œ¶[‰¶[†¶[ƒ¶[€¶[}¶[z¶[w¶[t¶[q¶[n¶[k¶[h¶[e¶[b¶[_¶[\¶[Y¶[V¶[S¶[P¶[M¶[J¶[G¶[D¶[A¶[>¶[;¶[8¶[5¶[2¶[/¶[,¶[)¶[&¶[#¶[ ¶[¶[¶[¶[¶[¶[¶[¶[¶[¶[¶Zÿ¶Zü¶Zù¶Zö¶Zó¶Zð¶Zí¶Zê¶Zç¶Zä¶Zá¶ZÞ¶ZÛ¶ZضZÕ¶ZÒ¶Z϶Z̶ZɶZƶZöZÀ¶Z½¶Zº¶Z·¶Z´¶Z±¶Z®¶Z«¶Z¨¶Z¥¶Z¢¶ZŸ¶Zœ¶Z™¶Z–¶Z“¶Z¶Z¶ZŠ¶Z‡¶Z„¶Z¶Z~¶Z{¶Zx¶Zu¶Zr¶Zo¶Zl¶Zi¶Zf¶Zc¶Z`¶Z]¶ZZ¶ZW¶ZT¶ZQ¶ZN¶ZK¶ZH¶ZE¶ZB¶Z?¶Z<¶Z9¶Z6¶Z3¶Z0¶Z-¶Z*¶Z'¶Z$¶Z!¶Z¶Z¶Z¶Z¶Z¶Z¶Z¶Z ¶Z¶Z¶Z +¶Y¶Y¶Y¶Xþ¶Xû¶Xø¶Xõ¶Xò¶Xï¶Xì¶Xé¶Xæ¶Xã¶Xà¶XݶXÚ¶X׶XÔ¶XѶXζX˶XȶXŶX¶X¿¶X¼¶X¹¶X¶¶X³¶X°¶X¶Xª¶X§¶X¤¶X¡¶Xž¶X›¶X˜¶X•¶X’¶X¶XŒ¶X‰¶X†¶Xƒ¶X€¶X}¶Xz¶Xw¶Xt¶Xq¶Xn¶Xk¶Xh¶Xe¶Xb¶X_¶X\¶XY¶XV¶XS¶XP¶XM¶XJ¶XG¶XD¶XA¶X>¶X;¶X8¶X5¶X2¶X/¶X,¶X)¶X&¶X#¶X ¶X¶X¶X¶X¶X¶X¶X¶X¶X¶X¶Wÿ¶Wü¶Wù¶Wö¶Wó¶Wð¶Wí¶Wê¶Wç¶Wä¶Wá¶WÞ¶WÛ¶WضWÕ¶WÒ¶W϶W̶WɶWƶWöWÀ¶W½¶Wº¶W·¶W´¶W±¶W®¶W«¶W¨¶W¥¶W¢¶WŸ¶Wœ¶W™¶W–¶W“¶W¶W¶WŠ¶W‡¶W„¶W¶W~¶W{¶Wx¶Wu¶Wr¶Wo¶Wl¶Wi¶Wf¶Wc¶W`¶W]¶WZ¶WW¶WT¶WQ¶WN¶WK¶WH¶WE¶WB¶W?¶W<¶W9¶W6¶W3¶W0¶W-¶W*¶W'¶W$¶W!¶W¶W¶W¶W¶W¶W¶W¶W ¶W¶W¶W +¶V¶V¶V¶Uþ¶Uû¶Uø¶Uõ¶Uò¶Uï¶Uì¶Ué¶Uæ¶Uã¶Uà¶UݶUÚ¶U׶UÔ¶UѶUζU˶UȶUŶU¶U¿¶U¼¶U¹¶U¶¶U³¶U°¶U¶Uª¶U§¶U¤¶U¡¶Už¶U›¶U˜¶U•¶U’¶U¶UŒ¶U‰¶U†¶Uƒ¶U€¶U}¶Uz¶Uw¶Ut¶Uq¶Un¶Uk¶Uh¶Ue¶Ub¶U_¶U\¶UY¶UV¶US¶UP¶UM¶UJ¶UG¶UD¶UA¶U>¶U;¶U8¶U5¶U2¶U/¶U,¶U)¶U&¶U#¶U ¶U¶U¶U¶U¶U¶U¶U¶U¶U¶U¶Tÿ¶Tü¶Tù¶Tö¶Tó¶Tð¶Tí¶Tê¶Tç¶Tä¶Tá¶TÞ¶TÛ¶TضTÕ¶TÒ¶T϶T̶TɶTƶTöTÀ¶T½¶Tº¶T·¶T´¶T±¶T®¶T«¶T¨¶T¥¶T¢¶TŸ¶Tœ¶T™¶T–¶T“¶T¶T¶TŠ¶T‡¶T„¶T¶T~¶T{¶Tx¶Tu¶Tr¶To¶Tl¶Ti¶Tf¶Tc¶T`¶T]¶TZ¶TW¶TT¶TQ¶TN¶TK¶TH¶TE¶TB¶T?¶T<¶T9¶T6¶T3¶T0¶T-¶T*¶T'¶T$¶T!¶T¶T¶T¶T¶T¶T¶T¶T ¶T¶T¶T +¶S¶S¶S¶Rþ¶Rû¶Rø¶Rõ¶Rò¶Rï¶Rì¶Ré¶Ræ¶Rã¶Rà¶RݶRÚ¶R׶RÔ¶RѶRζR˶RȶRŶR¶R¿¶R¼¶R¹¶R¶¶R³¶R°¶R¶Rª¶R§¶R¤¶R¡¶Rž¶R›¶R˜¶R•¶R’¶R¶RŒ¶R‰¶R†¶Rƒ¶R€¶R}¶Rz¶Rw¶Rt¶Rq¶Rn¶Rk¶Rh¶Re¶Rb¶R_¶R\¶RY¶RV¶RS¶RP¶RM¶RJ¶RG¶RD¶RA¶R>¶R;¶R8¶R5¶R2¶R/¶R,¶R)¶R&¶R#¶R ¶R¶R¶R¶R¶R¶R¶R¶R¶R¶R¶Qÿ¶Qü¶Qù¶Qö¶Qó¶Qð¶Qí¶Qê¶Qç¶Qä¶Qá¶QÞ¶QÛ¶QضQÕ¶QÒ¶Q϶Q̶QɶQƶQöQÀ¶Q½¶Qº¶Q·¶Q´¶Q±¶Q®¶Q«¶Q¨¶Q¥¶Q¢¶QŸ¶Qœ¶Q™¶Q–¶Q“¶Q¶Q¶QŠ¶Q‡¶Q„¶Q¶Q~¶Q{¶Qx¶Qu¶Qr¶Qo¶Ql¶Qi¶Qf¶Qc¶Q`¶Q]¶QZ¶QW¶QT¶QQ¶QN¶QK¶QH¶QE¶QB¶Q?¶Q<¶Q9¶Q6¶Q3¶Q0¶Q-¶Q*¶Q'¶Q$¶Q!¶Q¶Q¶Q¶Q¶Q¶Q¶Q¶Q ¶Q¶Q¶Q +¶P¶P¶P¶Oþ¶Oû¶Oø¶Oõ¶Oò¶Oï¶Oì¶Oé¶Oæ¶Oã¶Oà¶OݶOÚ¶O׶OÔ¶OѶOζO˶OȶOŶO¶O¿¶O¼¶O¹¶O¶¶O³¶O°¶O¶Oª¶O§¶O¤¶O¡¶Ož¶O›¶O˜¶O•¶O’¶O¶OŒ¶O‰¶O†¶Oƒ¶O€¶O}¶Oz¶Ow¶Ot¶Oq¶On¶Ok¶Oh¶Oe¶Ob¶O_¶O\¶OY¶OV¶OS¶OP¶OM¶OJ¶OG¶OD¶OA¶O>¶O;¶O8¶O5¶O2¶O/¶O,¶O)¶O&¶O#¶O ¶O¶O¶O¶O¶O¶O¶O¶O¶O¶O¶Nÿ¶Nü¶Nù¶Nö¶Nó¶Nð¶Ní¶Nê¶Nç¶Nä¶Ná¶NÞ¶NÛ¶NضNÕ¶NÒ¶N϶N̶NɶNƶNöNÀ¶N½¶Nº¶N·¶N´¶N±¶N®¶N«¶N¨¶N¥¶N¢¶NŸ¶Nœ¶N™¶N–¶N“¶N¶N¶NŠ¶N‡¶N„¶N¶N~¶N{¶Nx¶Nu¶Nr¶No¶Nl¶Ni¶Nf¶Nc¶N`¶N]¶NZ¶NW¶NT¶NQ¶NN¶NK¶NH¶NE¶NB¶N?¶N<¶N9¶N6¶N3¶N0¶N-¶N*¶N'¶N$¶N!¶N¶N¶N¶N¶N¶N¶N¶N ¶N¶N¶N +¶M¶M¶M¶Lþ¶Lû¶Lø¶Lõ¶Lò¶Lï¶Lì¶Lé¶Læ¶Lã¶Là¶LݶLÚ¶L׶LÔ¶LѶLζL˶LȶLŶL¶L¿¶L¼¶L¹¶L¶¶L³¶L°¶L¶Lª¶L§¶L¤¶L¡¶Lž¶L›¶L˜¶L•¶L’¶L¶LŒ¶L‰¶L†¶Lƒ¶L€¶L}¶Lz¶Lw¶Lt¶Lq¶Ln¶Lk¶Lh¶Le¶Lb¶L_¶L\¶LY¶LV¶LS¶LP¶LM¶LJ¶LG¶LD¶LA¶L>¶L;¶L8¶L5¶L2¶L/¶L,¶L)¶L&¶L#¶L ¶L¶L¶L¶L¶L¶L¶L¶L¶L¶L¶Kÿ¶Kü¶Kù¶Kö¶Kó¶Kð¶Kí¶Kê¶Kç¶Kä¶Ká¶KÞ¶KÛ¶KضKÕ¶KÒ¶K϶K̶KɶKƶKöKÀ¶K½¶Kº¶K·¶K´¶K±¶K®¶K«¶K¨¶K¥¶K¢¶KŸ¶Kœ¶K™¶K–¶K“¶K¶K¶KŠ¶K‡¶K„¶K¶K~¶K{¶Kx¶Ku¶Kr¶Ko¶Kl¶Ki¶Kf¶Kc¶K`¶K]¶KZ¶KW¶KT¶KQ¶KN¶KK¶KH¶KE¶KB¶K?¶K<¶K9¶K6¶K3¶K0¶K-¶K*¶K'¶K$¶K!¶K¶K¶K¶K¶K¶K¶K¶K ¶K¶K¶K +¶J¶J¶J¶Iþ¶Iû¶Iø¶Iõ¶Iò¶Iï¶Iì¶Ié¶Iæ¶Iã¶Ià¶IݶIÚ¶I׶IÔ¶IѶIζI˶IȶIŶI¶I¿¶I¼¶I¹¶I¶¶I³¶I°¶I¶Iª¶I§¶I¤¶I¡¶Iž¶I›¶I˜¶I•¶I’¶I¶IŒ¶I‰¶I†¶Iƒ¶I€¶I}¶Iz¶Iw¶It¶Iq¶In¶Ik¶Ih¶Ie¶Ib¶I_¶I\¶IY¶IV¶IS¶IP¶IM¶IJ¶IG¶ID¶IA¶I>¶I;¶I8¶I5¶I2¶I/¶I,¶I)¶I&¶I#¶I ¶I¶I¶I¶I¶I¶I¶I¶I¶I¶I¶Hÿ¶Hü¶Hù¶Hö¶Hó¶Hð¶Hí¶Hê¶Hç¶Hä¶Há¶HÞ¶HÛ¶HضHÕ¶HÒ¶H϶H̶HɶHƶHöHÀ¶H½¶Hº¶H·¶H´¶H±¶H®¶H«¶H¨¶H¥¶H¢¶HŸ¶Hœ¶H™¶H–¶H“¶H¶H¶HŠ¶H‡¶H„¶H¶H~¶H{¶Hx¶Hu¶Hr¶Ho¶Hl¶Hi¶Hf¶Hc¶H`¶H]¶HZ¶HW¶HT¶HQ¶HN¶HK¶HH¶HE¶HB¶H?¶H<¶H9¶H6¶H3¶H0¶H-¶H*¶H'¶H$¶H!¶H¶H¶H¶H¶H¶H¶H¶H ¶H¶H¶H +¶G¶G¶G¶Fþ¶Fû¶Fø¶Fõ¶Fò¶Fï¶Fì¶Fé¶Fæ¶Fã¶Fà¶FݶFÚ¶F׶FÔ¶FѶFζF˶FȶFŶF¶F¿¶F¼¶F¹¶F¶¶F³¶F°¶F¶Fª¶F§¶F¤¶F¡¶Fž¶F›¶F˜¶F•¶F’¶F¶FŒ¶F‰¶F†¶Fƒ¶F€¶F}¶Fz¶Fw¶Ft¶Fq¶Fn¶Fk¶Fh¶Fe¶Fb¶F_¶F\¶FY¶FV¶FS¶FP¶FM¶FJ¶FG¶FD¶FA¶F>¶F;¶F8¶F5¶F2¶F/¶F,¶F)¶F&¶F#¶F ¶F¶F¶F¶F¶F¶F¶F¶F¶F¶F¶Eÿ¶Eü¶Eù¶Eö¶Eó¶Eð¶Eí¶Eê¶Eç¶Eä¶Eá¶EÞ¶EÛ¶EضEÕ¶EÒ¶E϶E̶EɶEƶEöEÀ¶E½¶Eº¶E·¶E´¶E±¶E®¶E«¶E¨¶E¥¶E¢¶EŸ¶Eœ¶E™¶E–¶E“¶E¶E¶EŠ¶E‡¶E„¶E¶E~¶E{¶Ex¶Eu¶Er¶Eo¶El¶Ei¶Ef¶Ec¶E`¶E]¶EZ¶EW¶ET¶EQ¶EN¶EK¶EH¶EE¶EB¶E?¶E<¶E9¶E6¶E3¶E0¶E-¶E*¶E'¶E$¶E!¶E¶E¶E¶E¶E¶E¶E¶E ¶E¶E¶E +¶D¶D¶D¶Cþ¶Cû¶Cø¶Cõ¶Cò¶Cï¶Cì¶Cé¶Cæ¶Cã¶Cà¶CݶCÚ¶C׶CÔ¶CѶCζC˶CȶCŶC¶C¿¶C¼¶C¹¶C¶¶C³¶C°¶C¶Cª¶C§¶C¤¶C¡¶Cž¶C›¶C˜¶C•¶C’¶C¶CŒ¶C‰¶C†¶Cƒ¶C€¶C}¶Cz¶Cw¶Ct¶Cq¶Cn¶Ck¶Ch¶Ce¶Cb¶C_¶C\¶CY¶CV¶CS¶CP¶CM¶CJ¶CG¶CD¶CA¶C>¶C;¶C8¶C5¶C2¶C/¶C,¶C)¶C&¶C#¶C ¶C¶C¶C¶C¶C¶C¶C¶C¶C¶C¶Bÿ¶Bü¶Bù¶Bö¶Bó¶Bð¶Bí¶Bê¶Bç¶Bä¶Bá¶BÞ¶BÛ¶BضBÕ¶BÒ¶B϶B̶BɶBƶBöBÀ¶B½¶Bº¶B·¶B´¶B±¶B®¶B«¶B¨¶B¥¶B¢¶BŸ¶Bœ¶B™¶B–¶B“¶B¶B¶BŠ¶B‡¶B„¶B¶B~¶B{¶Bx¶Bu¶Br¶Bo¶Bl¶Bi¶Bf¶Bc¶B`¶B]¶BZ¶BW¶BT¶BQ¶BN¶BK¶BH¶BE¶BB¶B?¶B<¶B9¶B6¶B3¶B0¶B-¶B*¶B'¶B$¶B!¶B¶B¶B¶B¶B¶B¶B¶B ¶B¶B¶B +¶A¶A¶A¶@þ¶@û¶@ø¶@õ¶@ò¶@ï¶@ì¶@é¶@æ¶@ã¶@à¶@ݶ@Ú¶@׶@Ô¶@Ѷ@ζ@˶@ȶ@Ŷ@¶@¿¶@¼¶@¹¶@¶¶@³¶@°¶@¶@ª¶@§¶@¤¶@¡¶@ž¶@›¶@˜¶@•¶@’¶@¶@Œ¶@‰¶@†¶@ƒ¶@€¶þ¶ü¶ú¶ø¶ö¶ô¶ò¶ð¶î¶ì¶ê¶è¶æ¶ä¶â¶à¶Þ¶Ü¶Ú¶Ø¶Ö¶Ô¶Ò¶Ð¶Î¶Ì¶Ê¶È¶Æ¶Ä¶Â¶À¶¾¶¼¶º¶¸¶¶¶´¶²¶°¶®¶¬¶ª¶¨¶¦¶¤¶¢¶ ¶ž¶œ¶š¶˜¶–¶”¶’¶¶Ž¶Œ¶Š¶ˆ¶†¶„¶‚¶€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a8468104c65a6002fd3a9d4ac39f3ee34c21ce4a b/webm_parser/fuzzing/corpus/a8468104c65a6002fd3a9d4ac39f3ee34c21ce4a new file mode 100644 index 0000000..9b15205 --- /dev/null +++ b/webm_parser/fuzzing/corpus/a8468104c65a6002fd3a9d4ac39f3ee34c21ce4a @@ -0,0 +1 @@ +S€gTÃgŠss‡cÀ„hÊÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a899424027f1d69a05384355858311a6fa3940a3 b/webm_parser/fuzzing/corpus/a899424027f1d69a05384355858311a6fa3940a3 new file mode 100644 index 0000000..3ef41ca --- /dev/null +++ b/webm_parser/fuzzing/corpus/a899424027f1d69a05384355858311a6fa3940a3 @@ -0,0 +1 @@ +S€gT®kˆ®†à„T°
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/a8a5de5a86a16952aacdf602120f27807294c3ec b/webm_parser/fuzzing/corpus/a8a5de5a86a16952aacdf602120f27807294c3ec new file mode 100644 index 0000000..865b44e --- /dev/null +++ b/webm_parser/fuzzing/corpus/a8a5de5a86a16952aacdf602120f27807294c3ec @@ -0,0 +1 @@ +S€g‘TÃgŒss‰cÀ†hʃ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/aa65229e62d7cb8048d2f5911226b177e8e53cab b/webm_parser/fuzzing/corpus/aa65229e62d7cb8048d2f5911226b177e8e53cab new file mode 100644 index 0000000..80476c0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/aa65229e62d7cb8048d2f5911226b177e8e53cab @@ -0,0 +1 @@ +S€gŠT®k…®ƒ×ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/aaa96713f8ccb0bbd5c8e91715d4b86e7b338676 b/webm_parser/fuzzing/corpus/aaa96713f8ccb0bbd5c8e91715d4b86e7b338676 Binary files differnew file mode 100644 index 0000000..1fa146d --- /dev/null +++ b/webm_parser/fuzzing/corpus/aaa96713f8ccb0bbd5c8e91715d4b86e7b338676 diff --git a/webm_parser/fuzzing/corpus/aae6354e5ba12ee3ad89fabfd72f5368a639d30f b/webm_parser/fuzzing/corpus/aae6354e5ba12ee3ad89fabfd72f5368a639d30f Binary files differnew file mode 100644 index 0000000..fb16013 --- /dev/null +++ b/webm_parser/fuzzing/corpus/aae6354e5ba12ee3ad89fabfd72f5368a639d30f diff --git a/webm_parser/fuzzing/corpus/ab6f3dc497f93f251ebacc153409b1eb8e05e2db b/webm_parser/fuzzing/corpus/ab6f3dc497f93f251ebacc153409b1eb8e05e2db new file mode 100644 index 0000000..182a5d9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ab6f3dc497f93f251ebacc153409b1eb8e05e2db @@ -0,0 +1 @@ +EߣˆB‚ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/abf5b18d1c1155d3e455c8b781948498f364965e b/webm_parser/fuzzing/corpus/abf5b18d1c1155d3e455c8b781948498f364965e Binary files differnew file mode 100644 index 0000000..e8c81d0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/abf5b18d1c1155d3e455c8b781948498f364965e diff --git a/webm_parser/fuzzing/corpus/abfe5dbf594a2f22173fae7ca8de28b4e3a40099 b/webm_parser/fuzzing/corpus/abfe5dbf594a2f22173fae7ca8de28b4e3a40099 new file mode 100644 index 0000000..f0c009c --- /dev/null +++ b/webm_parser/fuzzing/corpus/abfe5dbf594a2f22173fae7ca8de28b4e3a40099 @@ -0,0 +1 @@ +S€gŒT®k‡®…àƒ°ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ac842f2cc55d7f193273a35f8af3521bfe2317d0 b/webm_parser/fuzzing/corpus/ac842f2cc55d7f193273a35f8af3521bfe2317d0 Binary files differnew file mode 100644 index 0000000..77a070c --- /dev/null +++ b/webm_parser/fuzzing/corpus/ac842f2cc55d7f193273a35f8af3521bfe2317d0 diff --git a/webm_parser/fuzzing/corpus/ac923c36dd85ff5cb2a0c5b29c701999c5f2eaa0 b/webm_parser/fuzzing/corpus/ac923c36dd85ff5cb2a0c5b29c701999c5f2eaa0 Binary files differnew file mode 100644 index 0000000..6375039 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ac923c36dd85ff5cb2a0c5b29c701999c5f2eaa0 diff --git a/webm_parser/fuzzing/corpus/acc6e100b519d6408a8c6d8aed19203d874a187a b/webm_parser/fuzzing/corpus/acc6e100b519d6408a8c6d8aed19203d874a187a Binary files differnew file mode 100644 index 0000000..2a83db5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/acc6e100b519d6408a8c6d8aed19203d874a187a diff --git a/webm_parser/fuzzing/corpus/aceb250195867e7d9bbf6eb6e0055251e4bb5d67 b/webm_parser/fuzzing/corpus/aceb250195867e7d9bbf6eb6e0055251e4bb5d67 Binary files differnew file mode 100644 index 0000000..c75ee54 --- /dev/null +++ b/webm_parser/fuzzing/corpus/aceb250195867e7d9bbf6eb6e0055251e4bb5d67 diff --git a/webm_parser/fuzzing/corpus/acf1dd17e6a2848dfe17fb0d76cef2ce7d59ebdd b/webm_parser/fuzzing/corpus/acf1dd17e6a2848dfe17fb0d76cef2ce7d59ebdd new file mode 100644 index 0000000..3f65b28 --- /dev/null +++ b/webm_parser/fuzzing/corpus/acf1dd17e6a2848dfe17fb0d76cef2ce7d59ebdd @@ -0,0 +1 @@ +S€gTÃgŠss‡cÀ„hÊ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ad94c2bda2ae4afa0f7264070fd642bf37aae596 b/webm_parser/fuzzing/corpus/ad94c2bda2ae4afa0f7264070fd642bf37aae596 Binary files differnew file mode 100644 index 0000000..038d2d9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ad94c2bda2ae4afa0f7264070fd642bf37aae596 diff --git a/webm_parser/fuzzing/corpus/adad2ca7ab313add6e955f704719e03d5229e4d0 b/webm_parser/fuzzing/corpus/adad2ca7ab313add6e955f704719e03d5229e4d0 new file mode 100644 index 0000000..2105af1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/adad2ca7ab313add6e955f704719e03d5229e4d0 @@ -0,0 +1 @@ +ã
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/adb1f8cd3b68c076d81311071aee2a1c3785c7cb b/webm_parser/fuzzing/corpus/adb1f8cd3b68c076d81311071aee2a1c3785c7cb new file mode 100644 index 0000000..4710f7f --- /dev/null +++ b/webm_parser/fuzzing/corpus/adb1f8cd3b68c076d81311071aee2a1c3785c7cb @@ -0,0 +1 @@ +S€gŒT®k‡®…œƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ade30c327d9f51daf37cb3e39fdb15125b84a257 b/webm_parser/fuzzing/corpus/ade30c327d9f51daf37cb3e39fdb15125b84a257 new file mode 100644 index 0000000..4bc05f4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ade30c327d9f51daf37cb3e39fdb15125b84a257 @@ -0,0 +1 @@ +S€gT®kˆ®†à„S¸
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ae783661f52d36f9d6b87c8394ee6f2d61dae640 b/webm_parser/fuzzing/corpus/ae783661f52d36f9d6b87c8394ee6f2d61dae640 new file mode 100644 index 0000000..d5fe0e2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ae783661f52d36f9d6b87c8394ee6f2d61dae640 @@ -0,0 +1 @@ +S€g“M›tŽM»‹S«ˆMatroska
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ae8da06c3c69076c06108219e19c8b36dba05a5c b/webm_parser/fuzzing/corpus/ae8da06c3c69076c06108219e19c8b36dba05a5c new file mode 100644 index 0000000..78a347a --- /dev/null +++ b/webm_parser/fuzzing/corpus/ae8da06c3c69076c06108219e19c8b36dba05a5c @@ -0,0 +1 @@ +Eߣ8S€g;€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/aec8a29238dba3c37f45f2e2e4e64b0e7fa60a74 b/webm_parser/fuzzing/corpus/aec8a29238dba3c37f45f2e2e4e64b0e7fa60a74 new file mode 100644 index 0000000..359bfe6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/aec8a29238dba3c37f45f2e2e4e64b0e7fa60a74 @@ -0,0 +1 @@ +S€gI©f‹D‰ˆ?ùãw›—ô¨
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/affba8e4061bf260b1bdf8a815675cc3b4ebefa5 b/webm_parser/fuzzing/corpus/affba8e4061bf260b1bdf8a815675cc3b4ebefa5 Binary files differnew file mode 100644 index 0000000..94b2f2d --- /dev/null +++ b/webm_parser/fuzzing/corpus/affba8e4061bf260b1bdf8a815675cc3b4ebefa5 diff --git a/webm_parser/fuzzing/corpus/b01c4e2657a230e2b600a20da6f107b0dfe2467c b/webm_parser/fuzzing/corpus/b01c4e2657a230e2b600a20da6f107b0dfe2467c new file mode 100644 index 0000000..de433e0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b01c4e2657a230e2b600a20da6f107b0dfe2467c @@ -0,0 +1 @@ +S€gTÃgŠss‡gÈ„Dz!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b0798ecae4d9e56eefb0c1d95b657865938e62d6 b/webm_parser/fuzzing/corpus/b0798ecae4d9e56eefb0c1d95b657865938e62d6 Binary files differnew file mode 100644 index 0000000..f323a5e --- /dev/null +++ b/webm_parser/fuzzing/corpus/b0798ecae4d9e56eefb0c1d95b657865938e62d6 diff --git a/webm_parser/fuzzing/corpus/b0ab4f92df810edf4371baad1d4bbd95d360c607 b/webm_parser/fuzzing/corpus/b0ab4f92df810edf4371baad1d4bbd95d360c607 new file mode 100644 index 0000000..02f58e2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b0ab4f92df810edf4371baad1d4bbd95d360c607 @@ -0,0 +1 @@ +S€gŒC¶u‡ …›ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b14e5f5f6d29b6b71e7fec03eaf7d8237c0c6b2e b/webm_parser/fuzzing/corpus/b14e5f5f6d29b6b71e7fec03eaf7d8237c0c6b2e Binary files differnew file mode 100644 index 0000000..32796f0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b14e5f5f6d29b6b71e7fec03eaf7d8237c0c6b2e diff --git a/webm_parser/fuzzing/corpus/b1708df7f6dee0f2feb11f8b6330be0fcffc1f58 b/webm_parser/fuzzing/corpus/b1708df7f6dee0f2feb11f8b6330be0fcffc1f58 Binary files differnew file mode 100644 index 0000000..4a4eccf --- /dev/null +++ b/webm_parser/fuzzing/corpus/b1708df7f6dee0f2feb11f8b6330be0fcffc1f58 diff --git a/webm_parser/fuzzing/corpus/b1af4f4e86890fad6fac96a23956405c550778be b/webm_parser/fuzzing/corpus/b1af4f4e86890fad6fac96a23956405c550778be Binary files differnew file mode 100644 index 0000000..77bb0f0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b1af4f4e86890fad6fac96a23956405c550778be diff --git a/webm_parser/fuzzing/corpus/b1efd9e687d79e3f5a75eba02bd80bedb72350ba b/webm_parser/fuzzing/corpus/b1efd9e687d79e3f5a75eba02bd80bedb72350ba Binary files differnew file mode 100644 index 0000000..348fa2c --- /dev/null +++ b/webm_parser/fuzzing/corpus/b1efd9e687d79e3f5a75eba02bd80bedb72350ba diff --git a/webm_parser/fuzzing/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd b/webm_parser/fuzzing/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd new file mode 100644 index 0000000..b59c594 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd @@ -0,0 +1 @@ +™
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b2786e0b9d6e7b39165eb4e87e3110362e9e6660 b/webm_parser/fuzzing/corpus/b2786e0b9d6e7b39165eb4e87e3110362e9e6660 new file mode 100644 index 0000000..6004cde --- /dev/null +++ b/webm_parser/fuzzing/corpus/b2786e0b9d6e7b39165eb4e87e3110362e9e6660 @@ -0,0 +1 @@ +EߣˆB‚@webmS€g©C¶u¤ç@«@
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b332536a77d6efcd379cfc2f9828291516cc1ff4 b/webm_parser/fuzzing/corpus/b332536a77d6efcd379cfc2f9828291516cc1ff4 new file mode 100644 index 0000000..2082647 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b332536a77d6efcd379cfc2f9828291516cc1ff4 @@ -0,0 +1 @@ +S€g–T®k‘®m€Œb@‰P5†Gçƒ'è€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b33962eca397f59591dbb9668e622cf99f2d7cda b/webm_parser/fuzzing/corpus/b33962eca397f59591dbb9668e622cf99f2d7cda Binary files differnew file mode 100644 index 0000000..9f6ee1d --- /dev/null +++ b/webm_parser/fuzzing/corpus/b33962eca397f59591dbb9668e622cf99f2d7cda diff --git a/webm_parser/fuzzing/corpus/b38b6e93da1b43441f88aa53370a9f00b35c3326 b/webm_parser/fuzzing/corpus/b38b6e93da1b43441f88aa53370a9f00b35c3326 Binary files differnew file mode 100644 index 0000000..7700315 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b38b6e93da1b43441f88aa53370a9f00b35c3326 diff --git a/webm_parser/fuzzing/corpus/b3e01674a1e4dd78e748782fcfc3add5523f51d8 b/webm_parser/fuzzing/corpus/b3e01674a1e4dd78e748782fcfc3add5523f51d8 new file mode 100644 index 0000000..3416407 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b3e01674a1e4dd78e748782fcfc3add5523f51d8 @@ -0,0 +1 @@ +×
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b53e7ef9aad70fcd80986696ebc586c03495b8ed b/webm_parser/fuzzing/corpus/b53e7ef9aad70fcd80986696ebc586c03495b8ed new file mode 100644 index 0000000..ee865d8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b53e7ef9aad70fcd80986696ebc586c03495b8ed @@ -0,0 +1 @@ +GáEߣˆB‚@webmS€g£*M'›t€I©f€C¶u€TSkk»€®€C§p€TÀ†g€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b540412c01f960f95edd2a1bc03f1b4447f2b4f2 b/webm_parser/fuzzing/corpus/b540412c01f960f95edd2a1bc03f1b4447f2b4f2 Binary files differnew file mode 100644 index 0000000..b77bc28 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b540412c01f960f95edd2a1bc03f1b4447f2b4f2 diff --git a/webm_parser/fuzzing/corpus/b62c2c591db32b26e997aa4ece577742db84428a b/webm_parser/fuzzing/corpus/b62c2c591db32b26e997aa4ece577742db84428a new file mode 100644 index 0000000..64a70ac --- /dev/null +++ b/webm_parser/fuzzing/corpus/b62c2c591db32b26e997aa4ece577742db84428a @@ -0,0 +1 @@ +S€g“C¶uŽ ŒŽ‡èƒÌè€Ì
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b62d99583f30c15c3c2dbed2f69c5e45075d7640 b/webm_parser/fuzzing/corpus/b62d99583f30c15c3c2dbed2f69c5e45075d7640 new file mode 100644 index 0000000..edf7ec3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b62d99583f30c15c3c2dbed2f69c5e45075d7640 @@ -0,0 +1 @@ +{©€gˆC¶uƒ«
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b62f98976c11d79674b019ea78a7ce4d6d78b479 b/webm_parser/fuzzing/corpus/b62f98976c11d79674b019ea78a7ce4d6d78b479 Binary files differnew file mode 100644 index 0000000..6a4f186 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b62f98976c11d79674b019ea78a7ce4d6d78b479 diff --git a/webm_parser/fuzzing/corpus/b6787dabeb5cd64ac85f1ec5a7cbeecfd48b2c5f b/webm_parser/fuzzing/corpus/b6787dabeb5cd64ac85f1ec5a7cbeecfd48b2c5f new file mode 100644 index 0000000..d043095 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b6787dabeb5cd64ac85f1ec5a7cbeecfd48b2c5f @@ -0,0 +1 @@ +S€g¼C§p·E¹´¶‘|°…@hello€€€€€€€€€…@B€@…@C¶@
sÄ€€€€€€„€–€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b82ebcf4d09ba28d835cb9667da603e46e3438eb b/webm_parser/fuzzing/corpus/b82ebcf4d09ba28d835cb9667da603e46e3438eb new file mode 100644 index 0000000..cea92d6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b82ebcf4d09ba28d835cb9667da603e46e3438eb @@ -0,0 +1 @@ +S€gC§p‹E¹ˆ¶†€„C~!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/b85c6bc2473aa12e22f91db3546dfa9d85d8b3d1 b/webm_parser/fuzzing/corpus/b85c6bc2473aa12e22f91db3546dfa9d85d8b3d1 Binary files differnew file mode 100644 index 0000000..5f82c67 --- /dev/null +++ b/webm_parser/fuzzing/corpus/b85c6bc2473aa12e22f91db3546dfa9d85d8b3d1 diff --git a/webm_parser/fuzzing/corpus/b86e98660980680890bcbf02cacdf568d530fa64 b/webm_parser/fuzzing/corpus/b86e98660980680890bcbf02cacdf568d530fa64 Binary files differnew file mode 100644 index 0000000..207162b --- /dev/null +++ b/webm_parser/fuzzing/corpus/b86e98660980680890bcbf02cacdf568d530fa64 diff --git a/webm_parser/fuzzing/corpus/b8d9beea35762009941189674c2cfcd14f81254a b/webm_parser/fuzzing/corpus/b8d9beea35762009941189674c2cfcd14f81254a new file mode 100644 index 0000000..25ec34a --- /dev/null +++ b/webm_parser/fuzzing/corpus/b8d9beea35762009941189674c2cfcd14f81254a @@ -0,0 +1 @@ +S€gŒT®k‡®…ˆƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ba517141fc9a468142a8d03d4ee395b4d4f30edc b/webm_parser/fuzzing/corpus/ba517141fc9a468142a8d03d4ee395b4d4f30edc new file mode 100644 index 0000000..aaea60d --- /dev/null +++ b/webm_parser/fuzzing/corpus/ba517141fc9a468142a8d03d4ee395b4d4f30edc @@ -0,0 +1 @@ +S€cŽDg‰ss†cS€g‡SÀ»€gÈk€‚»€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/bb0a596017cfc2185505d28065ab3cd238d0e2ea b/webm_parser/fuzzing/corpus/bb0a596017cfc2185505d28065ab3cd238d0e2ea new file mode 100644 index 0000000..1379811 --- /dev/null +++ b/webm_parser/fuzzing/corpus/bb0a596017cfc2185505d28065ab3cd238d0e2ea @@ -0,0 +1 @@ +S€gˆC§pƒE¹€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/bb6232815b373e441e2acfcca015f75c680eafac b/webm_parser/fuzzing/corpus/bb6232815b373e441e2acfcca015f75c680eafac Binary files differnew file mode 100644 index 0000000..04a597c --- /dev/null +++ b/webm_parser/fuzzing/corpus/bb6232815b373e441e2acfcca015f75c680eafac diff --git a/webm_parser/fuzzing/corpus/bbdf4fa36ba9d645399f72c74033fd9c2631aebb b/webm_parser/fuzzing/corpus/bbdf4fa36ba9d645399f72c74033fd9c2631aebb new file mode 100644 index 0000000..d277d55 --- /dev/null +++ b/webm_parser/fuzzing/corpus/bbdf4fa36ba9d645399f72c74033fd9c2631aebb @@ -0,0 +1 @@ +C|€C|€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/bc53c9f93974b4f13382c1d49b1e3ec374005ee2 b/webm_parser/fuzzing/corpus/bc53c9f93974b4f13382c1d49b1e3ec374005ee2 new file mode 100644 index 0000000..795792f --- /dev/null +++ b/webm_parser/fuzzing/corpus/bc53c9f93974b4f13382c1d49b1e3ec374005ee2 @@ -0,0 +1 @@ +S€gŒT®k‡®…ƒƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/bcacc9bcd3b9cc7dbda9c52c6e4a06a756a9de90 b/webm_parser/fuzzing/corpus/bcacc9bcd3b9cc7dbda9c52c6e4a06a756a9de90 Binary files differnew file mode 100644 index 0000000..b31cda9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/bcacc9bcd3b9cc7dbda9c52c6e4a06a756a9de90 diff --git a/webm_parser/fuzzing/corpus/bcc55f432bcf39c6ffc6d7e950612b35e9ea2ec8 b/webm_parser/fuzzing/corpus/bcc55f432bcf39c6ffc6d7e950612b35e9ea2ec8 Binary files differnew file mode 100644 index 0000000..1fb2833 --- /dev/null +++ b/webm_parser/fuzzing/corpus/bcc55f432bcf39c6ffc6d7e950612b35e9ea2ec8 diff --git a/webm_parser/fuzzing/corpus/bdb1cc868d6ced390f5d35c5f43da9f464fac464 b/webm_parser/fuzzing/corpus/bdb1cc868d6ced390f5d35c5f43da9f464fac464 new file mode 100644 index 0000000..44bb8cd --- /dev/null +++ b/webm_parser/fuzzing/corpus/bdb1cc868d6ced390f5d35c5f43da9f464fac464 @@ -0,0 +1 @@ +S€g»TÃg¶ss³gÈ©È„#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ€D‡dgÈ€g
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/bf67bb08e0abde692748031c297bc1c7910542f4 b/webm_parser/fuzzing/corpus/bf67bb08e0abde692748031c297bc1c7910542f4 Binary files differnew file mode 100644 index 0000000..8705e84 --- /dev/null +++ b/webm_parser/fuzzing/corpus/bf67bb08e0abde692748031c297bc1c7910542f4 diff --git a/webm_parser/fuzzing/corpus/bfc07c62ef2770d53d9188b260f531a8128a5c5e b/webm_parser/fuzzing/corpus/bfc07c62ef2770d53d9188b260f531a8128a5c5e Binary files differnew file mode 100644 index 0000000..341f94d --- /dev/null +++ b/webm_parser/fuzzing/corpus/bfc07c62ef2770d53d9188b260f531a8128a5c5e diff --git a/webm_parser/fuzzing/corpus/bfed121df31ff73b770ed7f27fc7f4da2fa73e0c b/webm_parser/fuzzing/corpus/bfed121df31ff73b770ed7f27fc7f4da2fa73e0c Binary files differnew file mode 100644 index 0000000..7b2c561 --- /dev/null +++ b/webm_parser/fuzzing/corpus/bfed121df31ff73b770ed7f27fc7f4da2fa73e0c diff --git a/webm_parser/fuzzing/corpus/c01816206d93691165e6e3a1924cf9fbc9cf39bd b/webm_parser/fuzzing/corpus/c01816206d93691165e6e3a1924cf9fbc9cf39bd new file mode 100644 index 0000000..b99d57a --- /dev/null +++ b/webm_parser/fuzzing/corpus/c01816206d93691165e6e3a1924cf9fbc9cf39bd @@ -0,0 +1 @@ +S€–gT®k‘®m€Œb@‰P5ÿ†GçƒGè€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c02be525edae59ad9d0d9dcbd790629dc9aaee9b b/webm_parser/fuzzing/corpus/c02be525edae59ad9d0d9dcbd790629dc9aaee9b Binary files differnew file mode 100644 index 0000000..67629fe --- /dev/null +++ b/webm_parser/fuzzing/corpus/c02be525edae59ad9d0d9dcbd790629dc9aaee9b diff --git a/webm_parser/fuzzing/corpus/c094ce0c13ee9a4ca37817d9f7dddc11b2d60177 b/webm_parser/fuzzing/corpus/c094ce0c13ee9a4ca37817d9f7dddc11b2d60177 new file mode 100644 index 0000000..9b44fb0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c094ce0c13ee9a4ca37817d9f7dddc11b2d60177 @@ -0,0 +1 @@ +S€g‘C¶uŒ Š¡‡¦…î:ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c0aab5486ad2e80bcc12fca9fb6653984aa68274 b/webm_parser/fuzzing/corpus/c0aab5486ad2e80bcc12fca9fb6653984aa68274 new file mode 100644 index 0000000..d643dc1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c0aab5486ad2e80bcc12fca9fb6653984aa68274 @@ -0,0 +1 @@ +E᜘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c13ca850db259c032cf24cdf6f2833c9d74529d0 b/webm_parser/fuzzing/corpus/c13ca850db259c032cf24cdf6f2833c9d74529d0 new file mode 100644 index 0000000..46ddda2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c13ca850db259c032cf24cdf6f2833c9d74529d0 @@ -0,0 +1 @@ +EߣˆB‚@webmS€g©C¶uÿÿÿ€«
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c1a8da6cdc8988e6a69961413803acbd1ee935e0 b/webm_parser/fuzzing/corpus/c1a8da6cdc8988e6a69961413803acbd1ee935e0 new file mode 100644 index 0000000..80a14f7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c1a8da6cdc8988e6a69961413803acbd1ee935e0 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶³sÄVTA‘@’@€@…@B€@…@C¶@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c25abc82a0470129f2d098ac65fabf34c4e11188 b/webm_parser/fuzzing/corpus/c25abc82a0470129f2d098ac65fabf34c4e11188 Binary files differnew file mode 100644 index 0000000..774e5f1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c25abc82a0470129f2d098ac65fabf34c4e11188 diff --git a/webm_parser/fuzzing/corpus/c2787d2cf1d95cbcd8b9bcd15d50f67f7e92ad9f b/webm_parser/fuzzing/corpus/c2787d2cf1d95cbcd8b9bcd15d50f67f7e92ad9f Binary files differnew file mode 100644 index 0000000..53359f3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c2787d2cf1d95cbcd8b9bcd15d50f67f7e92ad9f diff --git a/webm_parser/fuzzing/corpus/c2895ff545ebdc4140951c0ca956524d7a364b77 b/webm_parser/fuzzing/corpus/c2895ff545ebdc4140951c0ca956524d7a364b77 new file mode 100644 index 0000000..61489a2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c2895ff545ebdc4140951c0ca956524d7a364b77 @@ -0,0 +1 @@ +²Eߣ‡B
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c2cc55849ff4858bf80f1a4713187618d14a496d b/webm_parser/fuzzing/corpus/c2cc55849ff4858bf80f1a4713187618d14a496d Binary files differnew file mode 100644 index 0000000..b511eed --- /dev/null +++ b/webm_parser/fuzzing/corpus/c2cc55849ff4858bf80f1a4713187618d14a496d diff --git a/webm_parser/fuzzing/corpus/c2d63b1d75cf53ee3b955bb143036ca93ef3a256 b/webm_parser/fuzzing/corpus/c2d63b1d75cf53ee3b955bb143036ca93ef3a256 Binary files differnew file mode 100644 index 0000000..3fbf8e4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c2d63b1d75cf53ee3b955bb143036ca93ef3a256 diff --git a/webm_parser/fuzzing/corpus/c372a27f78a62ebc013958fa4953a8bc792db53c b/webm_parser/fuzzing/corpus/c372a27f78a62ebc013958fa4953a8bc792db53c new file mode 100644 index 0000000..8faacee --- /dev/null +++ b/webm_parser/fuzzing/corpus/c372a27f78a62ebc013958fa4953a8bc792db53c @@ -0,0 +1 @@ +Eߣÿÿÿÿÿÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c392963b395a7f92b3bef63fd34bc31ecd3029f3 b/webm_parser/fuzzing/corpus/c392963b395a7f92b3bef63fd34bc31ecd3029f3 Binary files differnew file mode 100644 index 0000000..91487ca --- /dev/null +++ b/webm_parser/fuzzing/corpus/c392963b395a7f92b3bef63fd34bc31ecd3029f3 diff --git a/webm_parser/fuzzing/corpus/c3b2749ab6c4d303bfd5da9ea9c8807e9f92d259 b/webm_parser/fuzzing/corpus/c3b2749ab6c4d303bfd5da9ea9c8807e9f92d259 Binary files differnew file mode 100644 index 0000000..e94d737 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c3b2749ab6c4d303bfd5da9ea9c8807e9f92d259 diff --git a/webm_parser/fuzzing/corpus/c4dd3c8cdd8d7c95603dd67f1cd873d5f9148b29 b/webm_parser/fuzzing/corpus/c4dd3c8cdd8d7c95603dd67f1cd873d5f9148b29 new file mode 100644 index 0000000..c5fa784 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c4dd3c8cdd8d7c95603dd67f1cd873d5f9148b29 @@ -0,0 +1 @@ +<
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c52fa8d16e520980e470d76e3fb4f6a612f0f3cf b/webm_parser/fuzzing/corpus/c52fa8d16e520980e470d76e3fb4f6a612f0f3cf Binary files differnew file mode 100644 index 0000000..d614d45 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c52fa8d16e520980e470d76e3fb4f6a612f0f3cf diff --git a/webm_parser/fuzzing/corpus/c56fb214efcd707e6fa68b803d9e7686fc2f4336 b/webm_parser/fuzzing/corpus/c56fb214efcd707e6fa68b803d9e7686fc2f4336 new file mode 100644 index 0000000..e03fe14 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c56fb214efcd707e6fa68b803d9e7686fc2f4336 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶žsÄVTA‘@’@€@…@B€@…@C¶@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c5902afe54998ebc5d8d59043227d379a8c2eee0 b/webm_parser/fuzzing/corpus/c5902afe54998ebc5d8d59043227d379a8c2eee0 Binary files differnew file mode 100644 index 0000000..d71d573 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c5902afe54998ebc5d8d59043227d379a8c2eee0 diff --git a/webm_parser/fuzzing/corpus/c6a16abeea323833079e97b1830610aa6c6eba91 b/webm_parser/fuzzing/corpus/c6a16abeea323833079e97b1830610aa6c6eba91 new file mode 100644 index 0000000..b48967d --- /dev/null +++ b/webm_parser/fuzzing/corpus/c6a16abeea323833079e97b1830610aa6c6eba91 @@ -0,0 +1 @@ +€gk‹®TEߣˆB‚@®‰á‡xµ„@ÉÛwebmS€g©C¶uÿÿÿ€«
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c6fcfd2a1f91a7f6a8c124f2637e60645be01006 b/webm_parser/fuzzing/corpus/c6fcfd2a1f91a7f6a8c124f2637e60645be01006 Binary files differnew file mode 100644 index 0000000..5aaebfb --- /dev/null +++ b/webm_parser/fuzzing/corpus/c6fcfd2a1f91a7f6a8c124f2637e60645be01006 diff --git a/webm_parser/fuzzing/corpus/c75b02a90370df1f54de2f63a4da8db22f2cf719 b/webm_parser/fuzzing/corpus/c75b02a90370df1f54de2f63a4da8db22f2cf719 new file mode 100644 index 0000000..0cb101b --- /dev/null +++ b/webm_parser/fuzzing/corpus/c75b02a90370df1f54de2f63a4da8db22f2cf719 @@ -0,0 +1 @@ +ŸEßE£‚B;
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c77cb763d73db279aebfb42d2b5dca3d705d3df7 b/webm_parser/fuzzing/corpus/c77cb763d73db279aebfb42d2b5dca3d705d3df7 Binary files differnew file mode 100644 index 0000000..cae0031 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c77cb763d73db279aebfb42d2b5dca3d705d3df7 diff --git a/webm_parser/fuzzing/corpus/c864ac8bce0353ecc7d5cb0ce5a1e77a5239201e b/webm_parser/fuzzing/corpus/c864ac8bce0353ecc7d5cb0ce5a1e77a5239201e new file mode 100644 index 0000000..d745027 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c864ac8bce0353ecc7d5cb0ce5a1e77a5239201e @@ -0,0 +1 @@ + ÿM
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c877c08a79a7408aed779d4a430c5db1bce26314 b/webm_parser/fuzzing/corpus/c877c08a79a7408aed779d4a430c5db1bce26314 new file mode 100644 index 0000000..de29891 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c877c08a79a7408aed779d4a430c5db1bce26314 @@ -0,0 +1 @@ +S€gŒI©f‡D‰„@
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c8aff6e2e2dfb18be385483b871ac86ff6eac63f b/webm_parser/fuzzing/corpus/c8aff6e2e2dfb18be385483b871ac86ff6eac63f Binary files differnew file mode 100644 index 0000000..d553636 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c8aff6e2e2dfb18be385483b871ac86ff6eac63f diff --git a/webm_parser/fuzzing/corpus/c8c1c1f970bc809a75ad076bdb06275b6f72d078 b/webm_parser/fuzzing/corpus/c8c1c1f970bc809a75ad076bdb06275b6f72d078 new file mode 100644 index 0000000..f2f8214 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c8c1c1f970bc809a75ad076bdb06275b6f72d078 @@ -0,0 +1 @@ +Eߣ„B÷
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c995d52934ac188971f02d5dbdb3cdd70cb03267 b/webm_parser/fuzzing/corpus/c995d52934ac188971f02d5dbdb3cdd70cb03267 new file mode 100644 index 0000000..fe179c9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c995d52934ac188971f02d5dbdb3cdd70cb03267 @@ -0,0 +1 @@ +Tº…éJœœœœœœ¼œ"…¼¿À>sÅœŠ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/c9e7fc3e0e1015a1c15992447f678a495c3ea5dc b/webm_parser/fuzzing/corpus/c9e7fc3e0e1015a1c15992447f678a495c3ea5dc new file mode 100644 index 0000000..b784d77 --- /dev/null +++ b/webm_parser/fuzzing/corpus/c9e7fc3e0e1015a1c15992447f678a495c3ea5dc @@ -0,0 +1 @@ +S€g–C§p‘E¹Ž¶@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ca5b619ce1bbe23d519f5764d53458d2b85eb9fa b/webm_parser/fuzzing/corpus/ca5b619ce1bbe23d519f5764d53458d2b85eb9fa Binary files differnew file mode 100644 index 0000000..a7b7c19 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ca5b619ce1bbe23d519f5764d53458d2b85eb9fa diff --git a/webm_parser/fuzzing/corpus/cb0556c65c7381192c94324a3b1b8afb7e33fecc b/webm_parser/fuzzing/corpus/cb0556c65c7381192c94324a3b1b8afb7e33fecc Binary files differnew file mode 100644 index 0000000..2a7063e --- /dev/null +++ b/webm_parser/fuzzing/corpus/cb0556c65c7381192c94324a3b1b8afb7e33fecc diff --git a/webm_parser/fuzzing/corpus/cb1a093e6810c7f6c002a2a54ea390cf769c9949 b/webm_parser/fuzzing/corpus/cb1a093e6810c7f6c002a2a54ea390cf769c9949 Binary files differnew file mode 100644 index 0000000..6f2ab37 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cb1a093e6810c7f6c002a2a54ea390cf769c9949 diff --git a/webm_parser/fuzzing/corpus/cc1487af64aeefd7080e7678a04870dc85a7928a b/webm_parser/fuzzing/corpus/cc1487af64aeefd7080e7678a04870dc85a7928a Binary files differnew file mode 100644 index 0000000..815b907 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cc1487af64aeefd7080e7678a04870dc85a7928a diff --git a/webm_parser/fuzzing/corpus/cc71d2c9f5eae12acee133bd9e50d84881a1bd88 b/webm_parser/fuzzing/corpus/cc71d2c9f5eae12acee133bd9e50d84881a1bd88 Binary files differnew file mode 100644 index 0000000..2f87371 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cc71d2c9f5eae12acee133bd9e50d84881a1bd88 diff --git a/webm_parser/fuzzing/corpus/ccb8f962426683663972534c15354f70c3b34a10 b/webm_parser/fuzzing/corpus/ccb8f962426683663972534c15354f70c3b34a10 new file mode 100644 index 0000000..3b41d17 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ccb8f962426683663972534c15354f70c3b34a10 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gáÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/cd0771c4754dfcd9c89b9b8a02df96fed974850d b/webm_parser/fuzzing/corpus/cd0771c4754dfcd9c89b9b8a02df96fed974850d new file mode 100644 index 0000000..519a312 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cd0771c4754dfcd9c89b9b8a02df96fed974850d @@ -0,0 +1 @@ +’!EߣˆB‚@webm!S€g*M£'›t€I©f€C¶u€TSkk»€®€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/cd8899c66cbd92ee57f94e000744b32662258ba3 b/webm_parser/fuzzing/corpus/cd8899c66cbd92ee57f94e000744b32662258ba3 Binary files differnew file mode 100644 index 0000000..42db6e8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cd8899c66cbd92ee57f94e000744b32662258ba3 diff --git a/webm_parser/fuzzing/corpus/cdf83138f69f0f7c66c13b56028610ac39038b3c b/webm_parser/fuzzing/corpus/cdf83138f69f0f7c66c13b56028610ac39038b3c new file mode 100644 index 0000000..5d6c848 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cdf83138f69f0f7c66c13b56028610ac39038b3c @@ -0,0 +1 @@ +€€€€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ce0138cd7718397b365d4c15b0b14f666c307419 b/webm_parser/fuzzing/corpus/ce0138cd7718397b365d4c15b0b14f666c307419 new file mode 100644 index 0000000..56b36df --- /dev/null +++ b/webm_parser/fuzzing/corpus/ce0138cd7718397b365d4c15b0b14f666c307419 @@ -0,0 +1 @@ +Eߣ‡
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/cec5c7e50ef1d865c879563d1a6d677adb86695c b/webm_parser/fuzzing/corpus/cec5c7e50ef1d865c879563d1a6d677adb86695c new file mode 100644 index 0000000..dea66a5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cec5c7e50ef1d865c879563d1a6d677adb86695c @@ -0,0 +1 @@ +S€gT®kˆ®†à„Tº
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ceff1dfaf2de4e33d2e3c20aeb7ba4b98f97784c b/webm_parser/fuzzing/corpus/ceff1dfaf2de4e33d2e3c20aeb7ba4b98f97784c new file mode 100644 index 0000000..1826f3d --- /dev/null +++ b/webm_parser/fuzzing/corpus/ceff1dfaf2de4e33d2e3c20aeb7ba4b98f97784c @@ -0,0 +1 @@ +S€gŠC¶u…«ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/cf04b5d28cea1971478806979b256a030a671541 b/webm_parser/fuzzing/corpus/cf04b5d28cea1971478806979b256a030a671541 new file mode 100644 index 0000000..48b0b4c --- /dev/null +++ b/webm_parser/fuzzing/corpus/cf04b5d28cea1971478806979b256a030a671541 @@ -0,0 +1 @@ +S€g–T®k‘®m€Œb@‰P5†Gáƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/cf1cf6ad5b3554c3ffc86a859319445158665ea7 b/webm_parser/fuzzing/corpus/cf1cf6ad5b3554c3ffc86a859319445158665ea7 new file mode 100644 index 0000000..14d1fdb --- /dev/null +++ b/webm_parser/fuzzing/corpus/cf1cf6ad5b3554c3ffc86a859319445158665ea7 @@ -0,0 +1 @@ +S®Š€g…k\"µ;®œŸƒ×
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/cf2338960588d4a5f02a47f3ee96a556224aad75 b/webm_parser/fuzzing/corpus/cf2338960588d4a5f02a47f3ee96a556224aad75 Binary files differnew file mode 100644 index 0000000..a0ed68e --- /dev/null +++ b/webm_parser/fuzzing/corpus/cf2338960588d4a5f02a47f3ee96a556224aad75 diff --git a/webm_parser/fuzzing/corpus/cfb09018afa0eb1a829e556d9f8bceff40cb36bc b/webm_parser/fuzzing/corpus/cfb09018afa0eb1a829e556d9f8bceff40cb36bc new file mode 100644 index 0000000..31fc352 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cfb09018afa0eb1a829e556d9f8bceff40cb36bc @@ -0,0 +1 @@ +S€gI©f‹Daˆ4Vxš¼Þð
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/cfbe3d66d7eb3199440e8e911a93044cc3165c6c b/webm_parser/fuzzing/corpus/cfbe3d66d7eb3199440e8e911a93044cc3165c6c Binary files differnew file mode 100644 index 0000000..ba138c4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/cfbe3d66d7eb3199440e8e911a93044cc3165c6c diff --git a/webm_parser/fuzzing/corpus/d0a91f7984904976de592cb68e8832853fbec9bc b/webm_parser/fuzzing/corpus/d0a91f7984904976de592cb68e8832853fbec9bc Binary files differnew file mode 100644 index 0000000..d61a53a --- /dev/null +++ b/webm_parser/fuzzing/corpus/d0a91f7984904976de592cb68e8832853fbec9bc diff --git a/webm_parser/fuzzing/corpus/d22f52563c16725ce4a924dc05d6ac7d64798c43 b/webm_parser/fuzzing/corpus/d22f52563c16725ce4a924dc05d6ac7d64798c43 Binary files differnew file mode 100644 index 0000000..bf45633 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d22f52563c16725ce4a924dc05d6ac7d64798c43 diff --git a/webm_parser/fuzzing/corpus/d2709a1c9d96e72cb844eedca8bfe8440cdb0ef8 b/webm_parser/fuzzing/corpus/d2709a1c9d96e72cb844eedca8bfe8440cdb0ef8 new file mode 100644 index 0000000..c41d905 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d2709a1c9d96e72cb844eedca8bfe8440cdb0ef8 @@ -0,0 +1 @@ +S€gI©f‹Daˆþܺ˜vT2
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d2e385f61ad4b5d45a10a5a6cb85d72e84dea54b b/webm_parser/fuzzing/corpus/d2e385f61ad4b5d45a10a5a6cb85d72e84dea54b new file mode 100644 index 0000000..433cbfd --- /dev/null +++ b/webm_parser/fuzzing/corpus/d2e385f61ad4b5d45a10a5a6cb85d72e84dea54b @@ -0,0 +1 @@ +@p€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d2facb213561b30a5bcd012e4e01d0d5b0b26957 b/webm_parser/fuzzing/corpus/d2facb213561b30a5bcd012e4e01d0d5b0b26957 new file mode 100644 index 0000000..26ca861 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d2facb213561b30a5bcd012e4e01d0d5b0b26957 @@ -0,0 +1 @@ +S€gT®kŠ®ˆà†T²ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d300e5e46a825e5892fae1ba3c466c836f9e1da2 b/webm_parser/fuzzing/corpus/d300e5e46a825e5892fae1ba3c466c836f9e1da2 Binary files differnew file mode 100644 index 0000000..7b6f1d8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d300e5e46a825e5892fae1ba3c466c836f9e1da2 diff --git a/webm_parser/fuzzing/corpus/d302c69c881e230e6433283007d318ba437025c3 b/webm_parser/fuzzing/corpus/d302c69c881e230e6433283007d318ba437025c3 new file mode 100644 index 0000000..055ab79 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d302c69c881e230e6433283007d318ba437025c3 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P3ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d30c1b65bc141d1a15d4ed622ba182c96dacdf92 b/webm_parser/fuzzing/corpus/d30c1b65bc141d1a15d4ed622ba182c96dacdf92 Binary files differnew file mode 100644 index 0000000..aeb5e2e --- /dev/null +++ b/webm_parser/fuzzing/corpus/d30c1b65bc141d1a15d4ed622ba182c96dacdf92 diff --git a/webm_parser/fuzzing/corpus/d3adf09fe6fb1534157c1dc68eb61365b46b1c35 b/webm_parser/fuzzing/corpus/d3adf09fe6fb1534157c1dc68eb61365b46b1c35 new file mode 100644 index 0000000..ad8336b --- /dev/null +++ b/webm_parser/fuzzing/corpus/d3adf09fe6fb1534157c1dc68eb61365b46b1c35 @@ -0,0 +1 @@ +Eߣ’’pàÓ’
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d3c74d3a64dba86f98a31bb726587ec97a7e4531 b/webm_parser/fuzzing/corpus/d3c74d3a64dba86f98a31bb726587ec97a7e4531 new file mode 100644 index 0000000..c2ce861 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d3c74d3a64dba86f98a31bb726587ec97a7e4531 @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶ÿ¶€¶€¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€D¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€II diff --git a/webm_parser/fuzzing/corpus/d3c9846ab319f12fc646c23a532c780daa9e993f b/webm_parser/fuzzing/corpus/d3c9846ab319f12fc646c23a532c780daa9e993f new file mode 100644 index 0000000..6986002 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d3c9846ab319f12fc646c23a532c780daa9e993f @@ -0,0 +1 @@ +S€g™T®k”®’m€b@ŒP1€P2€P3€P5€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d3f03301b52cf4a830c7dd200ed8ccbc09e6ec94 b/webm_parser/fuzzing/corpus/d3f03301b52cf4a830c7dd200ed8ccbc09e6ec94 Binary files differnew file mode 100644 index 0000000..3910637 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d3f03301b52cf4a830c7dd200ed8ccbc09e6ec94 diff --git a/webm_parser/fuzzing/corpus/d4800745440dace38766db3520ffe7baa0bd78f2 b/webm_parser/fuzzing/corpus/d4800745440dace38766db3520ffe7baa0bd78f2 Binary files differnew file mode 100644 index 0000000..78d6b02 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d4800745440dace38766db3520ffe7baa0bd78f2 diff --git a/webm_parser/fuzzing/corpus/d4d6271bba704ba08c2678eb8b1bc4e457144f50 b/webm_parser/fuzzing/corpus/d4d6271bba704ba08c2678eb8b1bc4e457144f50 Binary files differnew file mode 100644 index 0000000..5715e68 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d4d6271bba704ba08c2678eb8b1bc4e457144f50 diff --git a/webm_parser/fuzzing/corpus/d54020f766061e80f445690c2b5694ed8af21e9d b/webm_parser/fuzzing/corpus/d54020f766061e80f445690c2b5694ed8af21e9d new file mode 100644 index 0000000..d0e8288 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d54020f766061e80f445690c2b5694ed8af21e9d @@ -0,0 +1 @@ +Gâ€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d588515f125ebe968fe6a81cb6df7cfc41969e68 b/webm_parser/fuzzing/corpus/d588515f125ebe968fe6a81cb6df7cfc41969e68 new file mode 100644 index 0000000..4b30c90 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d588515f125ebe968fe6a81cb6df7cfc41969e68 @@ -0,0 +1 @@ +EߣÿEߣ2
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d5ee5e4dc8fad9f1da43102d8322beb005a047c0 b/webm_parser/fuzzing/corpus/d5ee5e4dc8fad9f1da43102d8322beb005a047c0 new file mode 100644 index 0000000..d51648b --- /dev/null +++ b/webm_parser/fuzzing/corpus/d5ee5e4dc8fad9f1da43102d8322beb005a047c0 @@ -0,0 +1 @@ +Eß¾ˆB‚@webmS€g•C¶uÿÿÿÿ«@
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d62459bb217d3050bcd9e29a6327cf81f5ed68b9 b/webm_parser/fuzzing/corpus/d62459bb217d3050bcd9e29a6327cf81f5ed68b9 new file mode 100644 index 0000000..c4ec3c4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d62459bb217d3050bcd9e29a6327cf81f5ed68b9 @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ‚€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ‚€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI diff --git a/webm_parser/fuzzing/corpus/d6acbf1cb46845618ed0d5a322c8bd4879d16422 b/webm_parser/fuzzing/corpus/d6acbf1cb46845618ed0d5a322c8bd4879d16422 new file mode 100644 index 0000000..479a877 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d6acbf1cb46845618ed0d5a322c8bd4879d16422 @@ -0,0 +1 @@ +S€g»TÃg¶ss³gÈ©È„#‡dgÈ„]‡egÈ„D*dgÈ„]‡egÈ„D‡gÈ€D‡dgÈg
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d6fc8cdbb0f1517159531098e900e2dcc91edba0 b/webm_parser/fuzzing/corpus/d6fc8cdbb0f1517159531098e900e2dcc91edba0 new file mode 100644 index 0000000..d7384b7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d6fc8cdbb0f1517159531098e900e2dcc91edba0 @@ -0,0 +1 @@ +S€g‡S»k‚»€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d7a9bbd9875a60edf9c528b298ea72a2fad7d3d7 b/webm_parser/fuzzing/corpus/d7a9bbd9875a60edf9c528b298ea72a2fad7d3d7 Binary files differnew file mode 100644 index 0000000..e2c81b6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d7a9bbd9875a60edf9c528b298ea72a2fad7d3d7 diff --git a/webm_parser/fuzzing/corpus/d82e70046a544e95e81f6271dd2695f2599f0574 b/webm_parser/fuzzing/corpus/d82e70046a544e95e81f6271dd2695f2599f0574 new file mode 100644 index 0000000..a2fe29e --- /dev/null +++ b/webm_parser/fuzzing/corpus/d82e70046a544e95e81f6271dd2695f2599f0574 @@ -0,0 +1 @@ +S€gŽTÃg‰ss†cÀ€gÈ€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d84bc8dca7c8fcd227254c06f5b88eaaf7cd5fde b/webm_parser/fuzzing/corpus/d84bc8dca7c8fcd227254c06f5b88eaaf7cd5fde new file mode 100644 index 0000000..f5d0f36 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d84bc8dca7c8fcd227254c06f5b88eaaf7cd5fde @@ -0,0 +1 @@ +è
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d8bcb7dd21205e7126e700323b1d58817e9d9a6d b/webm_parser/fuzzing/corpus/d8bcb7dd21205e7126e700323b1d58817e9d9a6d Binary files differnew file mode 100644 index 0000000..b32e704 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d8bcb7dd21205e7126e700323b1d58817e9d9a6d diff --git a/webm_parser/fuzzing/corpus/d8e2628b4092b9bbab4f02041647f950503eeb48 b/webm_parser/fuzzing/corpus/d8e2628b4092b9bbab4f02041647f950503eeb48 Binary files differnew file mode 100644 index 0000000..6aa71bd --- /dev/null +++ b/webm_parser/fuzzing/corpus/d8e2628b4092b9bbab4f02041647f950503eeb48 diff --git a/webm_parser/fuzzing/corpus/d8ee4c2b79863a237c432efe7d43d99c8d0afb9b b/webm_parser/fuzzing/corpus/d8ee4c2b79863a237c432efe7d43d99c8d0afb9b Binary files differnew file mode 100644 index 0000000..db8723c --- /dev/null +++ b/webm_parser/fuzzing/corpus/d8ee4c2b79863a237c432efe7d43d99c8d0afb9b diff --git a/webm_parser/fuzzing/corpus/d8ee9724bf16ff336387723dcf27319c3be72e01 b/webm_parser/fuzzing/corpus/d8ee9724bf16ff336387723dcf27319c3be72e01 new file mode 100644 index 0000000..ce72a85 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d8ee9724bf16ff336387723dcf27319c3be72e01 @@ -0,0 +1 @@ +S€gT®kŠ®ˆà†Tºƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d9379969bc956ad623e4bab8bbe47270a880a62d b/webm_parser/fuzzing/corpus/d9379969bc956ad623e4bab8bbe47270a880a62d new file mode 100644 index 0000000..40bdf1a --- /dev/null +++ b/webm_parser/fuzzing/corpus/d9379969bc956ad623e4bab8bbe47270a880a62d @@ -0,0 +1 @@ +S€g‹C¶u† „u¢
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/d9ccb79b0e070dcc2f5ed8e15d99bc5ef86ecff7 b/webm_parser/fuzzing/corpus/d9ccb79b0e070dcc2f5ed8e15d99bc5ef86ecff7 new file mode 100644 index 0000000..4c2c858 --- /dev/null +++ b/webm_parser/fuzzing/corpus/d9ccb79b0e070dcc2f5ed8e15d99bc5ef86ecff7 @@ -0,0 +1 @@ +EߣˆB‚@webmS€g©C¶u¤ç@«@£…ÿÿÿ @
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/db83586ca6266e03067e9a9772ea728ab770ad9a b/webm_parser/fuzzing/corpus/db83586ca6266e03067e9a9772ea728ab770ad9a Binary files differnew file mode 100644 index 0000000..0b49ef8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/db83586ca6266e03067e9a9772ea728ab770ad9a diff --git a/webm_parser/fuzzing/corpus/dcc63c06ed2790d1380bdb9281fe8677f439c76d b/webm_parser/fuzzing/corpus/dcc63c06ed2790d1380bdb9281fe8677f439c76d Binary files differnew file mode 100644 index 0000000..c5769be --- /dev/null +++ b/webm_parser/fuzzing/corpus/dcc63c06ed2790d1380bdb9281fe8677f439c76d diff --git a/webm_parser/fuzzing/corpus/dd509e9a4660ec34b8f9dc23441c6df4ff97c34d b/webm_parser/fuzzing/corpus/dd509e9a4660ec34b8f9dc23441c6df4ff97c34d new file mode 100644 index 0000000..2a77375 --- /dev/null +++ b/webm_parser/fuzzing/corpus/dd509e9a4660ec34b8f9dc23441c6df4ff97c34d @@ -0,0 +1 @@ +S€g“C§pŽE¹„E¼E¹„E¼
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/dd72f8ff3a067dc7871438b6023e1ed0a4c5787b b/webm_parser/fuzzing/corpus/dd72f8ff3a067dc7871438b6023e1ed0a4c5787b new file mode 100644 index 0000000..dec5b01 --- /dev/null +++ b/webm_parser/fuzzing/corpus/dd72f8ff3a067dc7871438b6023e1ed0a4c5787b @@ -0,0 +1 @@ +S€gŽ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/dd75880c5ad488885260f4031a763c86a8084406 b/webm_parser/fuzzing/corpus/dd75880c5ad488885260f4031a763c86a8084406 Binary files differnew file mode 100644 index 0000000..ad23554 --- /dev/null +++ b/webm_parser/fuzzing/corpus/dd75880c5ad488885260f4031a763c86a8084406 diff --git a/webm_parser/fuzzing/corpus/dd962d74d04aa4aed270fd8e6b0ae9c4ac35fd19 b/webm_parser/fuzzing/corpus/dd962d74d04aa4aed270fd8e6b0ae9c4ac35fd19 new file mode 100644 index 0000000..dcb3af4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/dd962d74d04aa4aed270fd8e6b0ae9c4ac35fd19 @@ -0,0 +1 @@ +€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI diff --git a/webm_parser/fuzzing/corpus/dd9aa9a49dd790b2ce99c5af1933232d46a7f80d b/webm_parser/fuzzing/corpus/dd9aa9a49dd790b2ce99c5af1933232d46a7f80d new file mode 100644 index 0000000..28fd4f5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/dd9aa9a49dd790b2ce99c5af1933232d46a7f80d @@ -0,0 +1 @@ +S€gŒI©f‡D‰„@ÉÛ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ddad7630818a1caa8054d2d7280a1d01bdb33ca3 b/webm_parser/fuzzing/corpus/ddad7630818a1caa8054d2d7280a1d01bdb33ca3 Binary files differnew file mode 100644 index 0000000..5fdbe5b --- /dev/null +++ b/webm_parser/fuzzing/corpus/ddad7630818a1caa8054d2d7280a1d01bdb33ca3 diff --git a/webm_parser/fuzzing/corpus/ddb2eaf33960ce69d579a551a7b05733adcd52aa b/webm_parser/fuzzing/corpus/ddb2eaf33960ce69d579a551a7b05733adcd52aa Binary files differnew file mode 100644 index 0000000..9f07b96 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ddb2eaf33960ce69d579a551a7b05733adcd52aa diff --git a/webm_parser/fuzzing/corpus/de0d98cb997c0a0f7be127a46d8a24d8a003931d b/webm_parser/fuzzing/corpus/de0d98cb997c0a0f7be127a46d8a24d8a003931d Binary files differnew file mode 100644 index 0000000..5a5f555 --- /dev/null +++ b/webm_parser/fuzzing/corpus/de0d98cb997c0a0f7be127a46d8a24d8a003931d diff --git a/webm_parser/fuzzing/corpus/de0ff884898c83fb880498f1b8328f78701e6534 b/webm_parser/fuzzing/corpus/de0ff884898c83fb880498f1b8328f78701e6534 new file mode 100644 index 0000000..b9f4c9d --- /dev/null +++ b/webm_parser/fuzzing/corpus/de0ff884898c83fb880498f1b8328f78701e6534 @@ -0,0 +1 @@ +¦€Ý€ç¦€¦€Ý€ç¦€ÝuÝu
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/de9e0ed8ae29220e5b65e5b97eb3b254ccbe7e0c b/webm_parser/fuzzing/corpus/de9e0ed8ae29220e5b65e5b97eb3b254ccbe7e0c Binary files differnew file mode 100644 index 0000000..d48be42 --- /dev/null +++ b/webm_parser/fuzzing/corpus/de9e0ed8ae29220e5b65e5b97eb3b254ccbe7e0c diff --git a/webm_parser/fuzzing/corpus/dec5e8ffb35aa707d127a11a49e47cb59954e969 b/webm_parser/fuzzing/corpus/dec5e8ffb35aa707d127a11a49e47cb59954e969 new file mode 100644 index 0000000..9289f7e --- /dev/null +++ b/webm_parser/fuzzing/corpus/dec5e8ffb35aa707d127a11a49e47cb59954e969 @@ -0,0 +1 @@ +S€gTÃgŠss‡gÈ„D‡!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/df1b205e339e7199b5094fcf0ec3b8a8ca7a692c b/webm_parser/fuzzing/corpus/df1b205e339e7199b5094fcf0ec3b8a8ca7a692c Binary files differnew file mode 100644 index 0000000..337ffd0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/df1b205e339e7199b5094fcf0ec3b8a8ca7a692c diff --git a/webm_parser/fuzzing/corpus/df254daba2299f3ff2367e284efc53a3296d136b b/webm_parser/fuzzing/corpus/df254daba2299f3ff2367e284efc53a3296d136b new file mode 100644 index 0000000..f8834a2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/df254daba2299f3ff2367e284efc53a3296d136b @@ -0,0 +1 @@ +€€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/dfdcde31231b8b3d3fd4e0ef8ea88885f488c4db b/webm_parser/fuzzing/corpus/dfdcde31231b8b3d3fd4e0ef8ea88885f488c4db Binary files differnew file mode 100644 index 0000000..d7b04ea --- /dev/null +++ b/webm_parser/fuzzing/corpus/dfdcde31231b8b3d3fd4e0ef8ea88885f488c4db diff --git a/webm_parser/fuzzing/corpus/e01962d7dc1b94e5c4424ec7adae16a7c03b9773 b/webm_parser/fuzzing/corpus/e01962d7dc1b94e5c4424ec7adae16a7c03b9773 new file mode 100644 index 0000000..8d7f32a --- /dev/null +++ b/webm_parser/fuzzing/corpus/e01962d7dc1b94e5c4424ec7adae16a7c03b9773 @@ -0,0 +1 @@ +€€X
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e058723f2964bf1405ae043ddb99efb17d821e15 b/webm_parser/fuzzing/corpus/e058723f2964bf1405ae043ddb99efb17d821e15 new file mode 100644 index 0000000..a294613 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e058723f2964bf1405ae043ddb99efb17d821e15 @@ -0,0 +1 @@ +S€gC§pŠE¹‡¶…‘ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e09e306ef596dcd0e44bd3ef3c5f7019c7343f84 b/webm_parser/fuzzing/corpus/e09e306ef596dcd0e44bd3ef3c5f7019c7343f84 new file mode 100644 index 0000000..1abbf62 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e09e306ef596dcd0e44bd3ef3c5f7019c7343f84 @@ -0,0 +1 @@ +S€gT®k‹®‰á‡xµ„Û
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e154eb76d096c1e545dcad591a58a02c37cd71ff b/webm_parser/fuzzing/corpus/e154eb76d096c1e545dcad591a58a02c37cd71ff new file mode 100644 index 0000000..f575e8b --- /dev/null +++ b/webm_parser/fuzzing/corpus/e154eb76d096c1e545dcad591a58a02c37cd71ff @@ -0,0 +1 @@ +!EߣˆB‚@webmS€g£*M'›t€I©f€C¶u€TSkk»€®€C§p€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e198d2e4f2f3528c2ff46d769a9281ebb3cfb44a b/webm_parser/fuzzing/corpus/e198d2e4f2f3528c2ff46d769a9281ebb3cfb44a new file mode 100644 index 0000000..75c0df8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e198d2e4f2f3528c2ff46d769a9281ebb3cfb44a @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gâ!S€g”T®k®m€Šb@‡P5„Gâ!S€g›T®k–®”m€‘S€g›T®k–®”m€‘
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e2068af1e903f4a81cd6fa5f4022e62070c259ec b/webm_parser/fuzzing/corpus/e2068af1e903f4a81cd6fa5f4022e62070c259ec Binary files differnew file mode 100644 index 0000000..8ceb559 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e2068af1e903f4a81cd6fa5f4022e62070c259ec diff --git a/webm_parser/fuzzing/corpus/e2890330b5655cb277a581b8dd2eeba0d9061ba5 b/webm_parser/fuzzing/corpus/e2890330b5655cb277a581b8dd2eeba0d9061ba5 new file mode 100644 index 0000000..ffdb9c0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e2890330b5655cb277a581b8dd2eeba0d9061ba5 @@ -0,0 +1 @@ +S€g”T®k®m€Šb@‡P5„Gá
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e2e0767d055a7042c24a7acd5d5b6b7c093ad065 b/webm_parser/fuzzing/corpus/e2e0767d055a7042c24a7acd5d5b6b7c093ad065 new file mode 100644 index 0000000..145b54c --- /dev/null +++ b/webm_parser/fuzzing/corpus/e2e0767d055a7042c24a7acd5d5b6b7c093ad065 @@ -0,0 +1 @@ +S€gŽT®k‰®‡à…ºƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e394a8e21e2c43c42135daa034ad5aabb06acffb b/webm_parser/fuzzing/corpus/e394a8e21e2c43c42135daa034ad5aabb06acffb Binary files differnew file mode 100644 index 0000000..3bfae2e --- /dev/null +++ b/webm_parser/fuzzing/corpus/e394a8e21e2c43c42135daa034ad5aabb06acffb diff --git a/webm_parser/fuzzing/corpus/e3b200e97ec226a197e91f12103aaa53d5c37b0e b/webm_parser/fuzzing/corpus/e3b200e97ec226a197e91f12103aaa53d5c37b0e new file mode 100644 index 0000000..a2f27b7 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e3b200e97ec226a197e91f12103aaa53d5c37b0e @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶³sÄV…@¦¶@sä¶@sĶ@sĶ@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e45b077cda64c380ae1b0910bc81d010c27e1c93 b/webm_parser/fuzzing/corpus/e45b077cda64c380ae1b0910bc81d010c27e1c93 new file mode 100644 index 0000000..e05b783 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e45b077cda64c380ae1b0910bc81d010c27e1c93 @@ -0,0 +1 @@ +Sx€g%T:kŒ®Šm€‡b@„1
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e45e71ca01ebe01b01b0ca99b8f20a756c36b967 b/webm_parser/fuzzing/corpus/e45e71ca01ebe01b01b0ca99b8f20a756c36b967 new file mode 100644 index 0000000..3d1f9b0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e45e71ca01ebe01b01b0ca99b8f20a756c36b967 @@ -0,0 +1 @@ +$ö€TÃg€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e4ff4bd938c3737a02862fc0656a1859d384d066 b/webm_parser/fuzzing/corpus/e4ff4bd938c3737a02862fc0656a1859d384d066 new file mode 100644 index 0000000..635b823 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e4ff4bd938c3737a02862fc0656a1859d384d066 @@ -0,0 +1 @@ +S€gC§pˆE¹…¶ƒ’ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e5a1acb7e6f71bc1a2fedaa6173764dfd04c844c b/webm_parser/fuzzing/corpus/e5a1acb7e6f71bc1a2fedaa6173764dfd04c844c new file mode 100644 index 0000000..ca4a19c --- /dev/null +++ b/webm_parser/fuzzing/corpus/e5a1acb7e6f71bc1a2fedaa6173764dfd04c844c @@ -0,0 +1 @@ +S€g–T®k‘®m€Œb@‰P5†GçƒGè€S€gg—T®kÀ®m€b@ŠP5‡Gâ„;G
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e6081eeff4ddb76e88e87ef9e4b5f199f5f11c28 b/webm_parser/fuzzing/corpus/e6081eeff4ddb76e88e87ef9e4b5f199f5f11c28 Binary files differnew file mode 100644 index 0000000..7b80d1b --- /dev/null +++ b/webm_parser/fuzzing/corpus/e6081eeff4ddb76e88e87ef9e4b5f199f5f11c28 diff --git a/webm_parser/fuzzing/corpus/e73c42dd266c4d9671da0c7af09e98c02fc052fd b/webm_parser/fuzzing/corpus/e73c42dd266c4d9671da0c7af09e98c02fc052fd Binary files differnew file mode 100644 index 0000000..18e6ae2 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e73c42dd266c4d9671da0c7af09e98c02fc052fd diff --git a/webm_parser/fuzzing/corpus/e7b4559a77df21b73285243a8350b844775bb380 b/webm_parser/fuzzing/corpus/e7b4559a77df21b73285243a8350b844775bb380 Binary files differnew file mode 100644 index 0000000..7885c3c --- /dev/null +++ b/webm_parser/fuzzing/corpus/e7b4559a77df21b73285243a8350b844775bb380 diff --git a/webm_parser/fuzzing/corpus/e7dd34a80646a8c38ae1ec3a27c1358bab13b360 b/webm_parser/fuzzing/corpus/e7dd34a80646a8c38ae1ec3a27c1358bab13b360 new file mode 100644 index 0000000..8a7f10e --- /dev/null +++ b/webm_parser/fuzzing/corpus/e7dd34a80646a8c38ae1ec3a27c1358bab13b360 @@ -0,0 +1 @@ +S€gŠT®k…®ƒƒÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e80b344f943ff0ad9219277c4578d3b4100b71dc b/webm_parser/fuzzing/corpus/e80b344f943ff0ad9219277c4578d3b4100b71dc new file mode 100644 index 0000000..0f27b93 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e80b344f943ff0ad9219277c4578d3b4100b71dc @@ -0,0 +1 @@ +Eߣÿø£b
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e86e26200290d9d428c5e98e191ec874eb918fb6 b/webm_parser/fuzzing/corpus/e86e26200290d9d428c5e98e191ec874eb918fb6 new file mode 100644 index 0000000..dedb463 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e86e26200290d9d428c5e98e191ec874eb918fb6 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P2ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e873a3b8f5c3b716e6446df34279b837cf8d2c30 b/webm_parser/fuzzing/corpus/e873a3b8f5c3b716e6446df34279b837cf8d2c30 Binary files differnew file mode 100644 index 0000000..40a9456 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e873a3b8f5c3b716e6446df34279b837cf8d2c30 diff --git a/webm_parser/fuzzing/corpus/e8fb71319db98d8e8cd131a4eb82879bfbec14d0 b/webm_parser/fuzzing/corpus/e8fb71319db98d8e8cd131a4eb82879bfbec14d0 Binary files differnew file mode 100644 index 0000000..2bf9e0d --- /dev/null +++ b/webm_parser/fuzzing/corpus/e8fb71319db98d8e8cd131a4eb82879bfbec14d0 diff --git a/webm_parser/fuzzing/corpus/e93c3f14614595a2675993438b4c1bfaafdc02d0 b/webm_parser/fuzzing/corpus/e93c3f14614595a2675993438b4c1bfaafdc02d0 new file mode 100644 index 0000000..b0c4d91 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e93c3f14614595a2675993438b4c1bfaafdc02d0 @@ -0,0 +1 @@ +S€gT®kŠ®ˆà†T³ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e93faef2d77b7c467ae280ba433928d66d63ea63 b/webm_parser/fuzzing/corpus/e93faef2d77b7c467ae280ba433928d66d63ea63 new file mode 100644 index 0000000..231c621 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e93faef2d77b7c467ae280ba433928d66d63ea63 @@ -0,0 +1 @@ +S€gT®kˆ®†à„Tºÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e95401b11d974ba63270668d3c32c29e95ae85da b/webm_parser/fuzzing/corpus/e95401b11d974ba63270668d3c32c29e95ae85da new file mode 100644 index 0000000..8f657f6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e95401b11d974ba63270668d3c32c29e95ae85da @@ -0,0 +1 @@ +S€gC§pŠE¹‡¶…€ƒ…
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e9723cebe688912f684bcd19b48e5bc8efafaf2a b/webm_parser/fuzzing/corpus/e9723cebe688912f684bcd19b48e5bc8efafaf2a new file mode 100644 index 0000000..861afde --- /dev/null +++ b/webm_parser/fuzzing/corpus/e9723cebe688912f684bcd19b48e5bc8efafaf2a @@ -0,0 +1 @@ +ÿÿÿ€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e9a4389895c006d4b912e8ac1169229e6b2a66da b/webm_parser/fuzzing/corpus/e9a4389895c006d4b912e8ac1169229e6b2a66da Binary files differnew file mode 100644 index 0000000..cab0d08 --- /dev/null +++ b/webm_parser/fuzzing/corpus/e9a4389895c006d4b912e8ac1169229e6b2a66da diff --git a/webm_parser/fuzzing/corpus/e9dc3d10b47ea580404c8e80b844a9978fcf4747 b/webm_parser/fuzzing/corpus/e9dc3d10b47ea580404c8e80b844a9978fcf4747 new file mode 100644 index 0000000..565b13f --- /dev/null +++ b/webm_parser/fuzzing/corpus/e9dc3d10b47ea580404c8e80b844a9978fcf4747 @@ -0,0 +1 @@ +S€gŒM›t‡M»„S«
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/e9e2ac24e5674e7ee424e2b270e5abe84f1a15c9 b/webm_parser/fuzzing/corpus/e9e2ac24e5674e7ee424e2b270e5abe84f1a15c9 new file mode 100644 index 0000000..14d396f --- /dev/null +++ b/webm_parser/fuzzing/corpus/e9e2ac24e5674e7ee424e2b270e5abe84f1a15c9 @@ -0,0 +1 @@ +S€gTÃgŠss‡cÀ„cÅ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ea2b4df24b526aad253ab175334cb934b9d80b83 b/webm_parser/fuzzing/corpus/ea2b4df24b526aad253ab175334cb934b9d80b83 Binary files differnew file mode 100644 index 0000000..936aa37 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ea2b4df24b526aad253ab175334cb934b9d80b83 diff --git a/webm_parser/fuzzing/corpus/ea681d11486feeab2f080b06ddd2533575b7ace4 b/webm_parser/fuzzing/corpus/ea681d11486feeab2f080b06ddd2533575b7ace4 new file mode 100644 index 0000000..e16eeba --- /dev/null +++ b/webm_parser/fuzzing/corpus/ea681d11486feeab2f080b06ddd2533575b7ace4 @@ -0,0 +1 @@ +S€gŽC¶u‰ ‡Ž…èƒÌÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ebb747925360528a9f366f9a57730883c636b2c7 b/webm_parser/fuzzing/corpus/ebb747925360528a9f366f9a57730883c636b2c7 Binary files differnew file mode 100644 index 0000000..e6ea8c8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ebb747925360528a9f366f9a57730883c636b2c7 diff --git a/webm_parser/fuzzing/corpus/ec39abab70a8e1ff072eb082caa6ca77b1ae8087 b/webm_parser/fuzzing/corpus/ec39abab70a8e1ff072eb082caa6ca77b1ae8087 Binary files differnew file mode 100644 index 0000000..fbefd01 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ec39abab70a8e1ff072eb082caa6ca77b1ae8087 diff --git a/webm_parser/fuzzing/corpus/ecdd96d6cab2dc714a0b0ada1c4fcb18c75298e4 b/webm_parser/fuzzing/corpus/ecdd96d6cab2dc714a0b0ada1c4fcb18c75298e4 new file mode 100644 index 0000000..ed93df6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ecdd96d6cab2dc714a0b0ada1c4fcb18c75298e4 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶³sÄV…@B€@…@C¶@sĶ@sä¶@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ed008faa6c9001951f50588a1597e03931501343 b/webm_parser/fuzzing/corpus/ed008faa6c9001951f50588a1597e03931501343 new file mode 100644 index 0000000..fe1b376 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ed008faa6c9001951f50588a1597e03931501343 @@ -0,0 +1 @@ +S€gC¶uŠ ˆu¡…¦ƒî
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ed12e272a27a2fbdbfe5a6e78e49ed722ebd3c69 b/webm_parser/fuzzing/corpus/ed12e272a27a2fbdbfe5a6e78e49ed722ebd3c69 new file mode 100644 index 0000000..a666d51 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ed12e272a27a2fbdbfe5a6e78e49ed722ebd3c69 @@ -0,0 +1 @@ +S€gŒT®k‡®…àƒš
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ed799ba0608690ac68dd85c588004197b86e02bf b/webm_parser/fuzzing/corpus/ed799ba0608690ac68dd85c588004197b86e02bf new file mode 100644 index 0000000..082e9a5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ed799ba0608690ac68dd85c588004197b86e02bf @@ -0,0 +1 @@ +S€g@äC¶u@Þ @Û¡@Øc
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/edd982c5bd3030bde8c044760e9a678c2a9306f4 b/webm_parser/fuzzing/corpus/edd982c5bd3030bde8c044760e9a678c2a9306f4 new file mode 100644 index 0000000..48da75a --- /dev/null +++ b/webm_parser/fuzzing/corpus/edd982c5bd3030bde8c044760e9a678c2a9306f4 @@ -0,0 +1 @@ +S€g‘T®kŒ®Šm€‡b@„P2
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/ee82c97e35ec92ec3b0bbf911904a050b3aca633 b/webm_parser/fuzzing/corpus/ee82c97e35ec92ec3b0bbf911904a050b3aca633 Binary files differnew file mode 100644 index 0000000..696f0eb --- /dev/null +++ b/webm_parser/fuzzing/corpus/ee82c97e35ec92ec3b0bbf911904a050b3aca633 diff --git a/webm_parser/fuzzing/corpus/ef11f14feee00e3c198015e6bc76688e970e2d8c b/webm_parser/fuzzing/corpus/ef11f14feee00e3c198015e6bc76688e970e2d8c new file mode 100644 index 0000000..414c1b3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ef11f14feee00e3c198015e6bc76688e970e2d8c @@ -0,0 +1 @@ +S€g¯€ø
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/efa12e91e0d63c2353c120ca1ded7b36afbf57eb b/webm_parser/fuzzing/corpus/efa12e91e0d63c2353c120ca1ded7b36afbf57eb Binary files differnew file mode 100644 index 0000000..1673fc0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/efa12e91e0d63c2353c120ca1ded7b36afbf57eb diff --git a/webm_parser/fuzzing/corpus/efa14cba9bbaf749067b7bb8515a5d8a289fa389 b/webm_parser/fuzzing/corpus/efa14cba9bbaf749067b7bb8515a5d8a289fa389 new file mode 100644 index 0000000..7c34b99 --- /dev/null +++ b/webm_parser/fuzzing/corpus/efa14cba9bbaf749067b7bb8515a5d8a289fa389 @@ -0,0 +1 @@ +S€g‰I©f„WA!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/eff9bf13cd33ea50a8eacc5f2839cc4b5d67b2de b/webm_parser/fuzzing/corpus/eff9bf13cd33ea50a8eacc5f2839cc4b5d67b2de new file mode 100644 index 0000000..5f0ecb9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/eff9bf13cd33ea50a8eacc5f2839cc4b5d67b2de @@ -0,0 +1 @@ +Sg€„IŒë@D‡‰ÉÛ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f0143756917f0b2e374bd03e731cd64168180054 b/webm_parser/fuzzing/corpus/f0143756917f0b2e374bd03e731cd64168180054 new file mode 100644 index 0000000..6773d0f --- /dev/null +++ b/webm_parser/fuzzing/corpus/f0143756917f0b2e374bd03e731cd64168180054 @@ -0,0 +1 @@ +S€g‰C¶u„ ‚¢€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f0b9dcd4e1845f774bb0f42653b11040baee0765 b/webm_parser/fuzzing/corpus/f0b9dcd4e1845f774bb0f42653b11040baee0765 Binary files differnew file mode 100644 index 0000000..71caf14 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f0b9dcd4e1845f774bb0f42653b11040baee0765 diff --git a/webm_parser/fuzzing/corpus/f1a41ceb420b6b3df50b10f27108c133d4d07508 b/webm_parser/fuzzing/corpus/f1a41ceb420b6b3df50b10f27108c133d4d07508 Binary files differnew file mode 100644 index 0000000..a9c68c1 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f1a41ceb420b6b3df50b10f27108c133d4d07508 diff --git a/webm_parser/fuzzing/corpus/f39378fd978c6cdb4a8d08cdffc9034e2ca5b687 b/webm_parser/fuzzing/corpus/f39378fd978c6cdb4a8d08cdffc9034e2ca5b687 new file mode 100644 index 0000000..76de32c --- /dev/null +++ b/webm_parser/fuzzing/corpus/f39378fd978c6cdb4a8d08cdffc9034e2ca5b687 @@ -0,0 +1 @@ +S€g™T®k”®’m€b@ŒP5‰á€Gâ€Gç€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f39707a104112d13d9d6bcfbef0efe8dfd98270c b/webm_parser/fuzzing/corpus/f39707a104112d13d9d6bcfbef0efe8dfd98270c new file mode 100644 index 0000000..95f4c2c --- /dev/null +++ b/webm_parser/fuzzing/corpus/f39707a104112d13d9d6bcfbef0efe8dfd98270c @@ -0,0 +1 @@ +·
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f4341645a7d466113655d63c5aa00855904166c5 b/webm_parser/fuzzing/corpus/f4341645a7d466113655d63c5aa00855904166c5 new file mode 100644 index 0000000..919b544 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f4341645a7d466113655d63c5aa00855904166c5 @@ -0,0 +1 @@ +çÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f5511a42d83f94619ff8ca6c940cacc32bdc4834 b/webm_parser/fuzzing/corpus/f5511a42d83f94619ff8ca6c940cacc32bdc4834 Binary files differnew file mode 100644 index 0000000..9ba5fc3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f5511a42d83f94619ff8ca6c940cacc32bdc4834 diff --git a/webm_parser/fuzzing/corpus/f57a39fa918249e6941b4e770e15a8131ac16ea5 b/webm_parser/fuzzing/corpus/f57a39fa918249e6941b4e770e15a8131ac16ea5 new file mode 100644 index 0000000..7387db3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f57a39fa918249e6941b4e770e15a8131ac16ea5 @@ -0,0 +1 @@ +S€g½C§p¸E¹µ¶žsÄVTA‘@’@€@…@B€@…@C¶@sĶ@sĶ@sÄ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f5ac272a1dbf362e265210869aaf70ca410f1703 b/webm_parser/fuzzing/corpus/f5ac272a1dbf362e265210869aaf70ca410f1703 new file mode 100644 index 0000000..19acfef --- /dev/null +++ b/webm_parser/fuzzing/corpus/f5ac272a1dbf362e265210869aaf70ca410f1703 @@ -0,0 +1 @@ +S€g‘TÃgŒss‰cÀ†cŃ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f64c5ca1de57bcf323741f56754f53c642be8ab0 b/webm_parser/fuzzing/corpus/f64c5ca1de57bcf323741f56754f53c642be8ab0 new file mode 100644 index 0000000..a5222e4 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f64c5ca1de57bcf323741f56754f53c642be8ab0 @@ -0,0 +1 @@ +S€gC§p‹E¹ˆ¶†€„C|!
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f64dcdee393b4b0a3343f8e684c9db91a6eaeb6e b/webm_parser/fuzzing/corpus/f64dcdee393b4b0a3343f8e684c9db91a6eaeb6e Binary files differnew file mode 100644 index 0000000..103c0aa --- /dev/null +++ b/webm_parser/fuzzing/corpus/f64dcdee393b4b0a3343f8e684c9db91a6eaeb6e diff --git a/webm_parser/fuzzing/corpus/f69d2954da077043c6ae085e2771a702689314d5 b/webm_parser/fuzzing/corpus/f69d2954da077043c6ae085e2771a702689314d5 Binary files differnew file mode 100644 index 0000000..8474e60 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f69d2954da077043c6ae085e2771a702689314d5 diff --git a/webm_parser/fuzzing/corpus/f74e2203adb9c94ba80f7cc3214e3b3040e5675d b/webm_parser/fuzzing/corpus/f74e2203adb9c94ba80f7cc3214e3b3040e5675d Binary files differnew file mode 100644 index 0000000..0caab79 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f74e2203adb9c94ba80f7cc3214e3b3040e5675d diff --git a/webm_parser/fuzzing/corpus/f7906e7dc01323b9d3d6e298e8dc2386c8d152f6 b/webm_parser/fuzzing/corpus/f7906e7dc01323b9d3d6e298e8dc2386c8d152f6 new file mode 100644 index 0000000..d70e0ac --- /dev/null +++ b/webm_parser/fuzzing/corpus/f7906e7dc01323b9d3d6e298e8dc2386c8d152f6 @@ -0,0 +1 @@ +×À€gˆ¶Cƒu«
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f825cac511f3dc38a5ecf3aab3691b4c49ca8f0e b/webm_parser/fuzzing/corpus/f825cac511f3dc38a5ecf3aab3691b4c49ca8f0e Binary files differnew file mode 100644 index 0000000..8ea43ca --- /dev/null +++ b/webm_parser/fuzzing/corpus/f825cac511f3dc38a5ecf3aab3691b4c49ca8f0e diff --git a/webm_parser/fuzzing/corpus/f84c3f9e305172f2ae15dff0b4d955324b3a398e b/webm_parser/fuzzing/corpus/f84c3f9e305172f2ae15dff0b4d955324b3a398e Binary files differnew file mode 100644 index 0000000..c1987a9 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f84c3f9e305172f2ae15dff0b4d955324b3a398e diff --git a/webm_parser/fuzzing/corpus/f87cfad97831c33610fbbe34a04369bd93862464 b/webm_parser/fuzzing/corpus/f87cfad97831c33610fbbe34a04369bd93862464 Binary files differnew file mode 100644 index 0000000..c05f98f --- /dev/null +++ b/webm_parser/fuzzing/corpus/f87cfad97831c33610fbbe34a04369bd93862464 diff --git a/webm_parser/fuzzing/corpus/f8ebd7703fd3ba1a135b243fd947dbd61907d0f4 b/webm_parser/fuzzing/corpus/f8ebd7703fd3ba1a135b243fd947dbd61907d0f4 new file mode 100644 index 0000000..1d9fee6 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f8ebd7703fd3ba1a135b243fd947dbd61907d0f4 @@ -0,0 +1 @@ +S€g“T®kŽ®Œm€‰b@†P2ƒ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f926f6b2337650f8b518422c2f63a8869f47c742 b/webm_parser/fuzzing/corpus/f926f6b2337650f8b518422c2f63a8869f47c742 new file mode 100644 index 0000000..c30c8d0 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f926f6b2337650f8b518422c2f63a8869f47c742 @@ -0,0 +1 @@ +S€gT®kˆ®†m€ƒb@€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f939897b9fbe865c96020927dc81de9dc255d385 b/webm_parser/fuzzing/corpus/f939897b9fbe865c96020927dc81de9dc255d385 new file mode 100644 index 0000000..12352de --- /dev/null +++ b/webm_parser/fuzzing/corpus/f939897b9fbe865c96020927dc81de9dc255d385 @@ -0,0 +1 @@ +S€gˆC¶uƒçÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/f985f995ca7d1b691e6ae3b3ef57e8b3c7aa7129 b/webm_parser/fuzzing/corpus/f985f995ca7d1b691e6ae3b3ef57e8b3c7aa7129 new file mode 100644 index 0000000..e35f109 --- /dev/null +++ b/webm_parser/fuzzing/corpus/f985f995ca7d1b691e6ae3b3ef57e8b3c7aa7129 @@ -0,0 +1 @@ +€ÿm
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fa79e8ad34cabea4d3c434cc02ea1499069fcafc b/webm_parser/fuzzing/corpus/fa79e8ad34cabea4d3c434cc02ea1499069fcafc Binary files differnew file mode 100644 index 0000000..ac147e8 --- /dev/null +++ b/webm_parser/fuzzing/corpus/fa79e8ad34cabea4d3c434cc02ea1499069fcafc diff --git a/webm_parser/fuzzing/corpus/fa7a8dfdd46845ab0fd9b5b7004e37d0232941bf b/webm_parser/fuzzing/corpus/fa7a8dfdd46845ab0fd9b5b7004e37d0232941bf new file mode 100644 index 0000000..858ffc5 --- /dev/null +++ b/webm_parser/fuzzing/corpus/fa7a8dfdd46845ab0fd9b5b7004e37d0232941bf @@ -0,0 +1 @@ +S€gT®kˆ®†à„T³ÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fcb3054fde86111e2c346aa71af2456a1705902c b/webm_parser/fuzzing/corpus/fcb3054fde86111e2c346aa71af2456a1705902c new file mode 100644 index 0000000..318ea20 --- /dev/null +++ b/webm_parser/fuzzing/corpus/fcb3054fde86111e2c346aa71af2456a1705902c @@ -0,0 +1 @@ +S€g™TÃg”ss‡cÀ„hÊss‡cÀ„hÊ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fce30dcdf2f23b14c580c282a39e065d7aacbfe8 b/webm_parser/fuzzing/corpus/fce30dcdf2f23b14c580c282a39e065d7aacbfe8 new file mode 100644 index 0000000..394d54b --- /dev/null +++ b/webm_parser/fuzzing/corpus/fce30dcdf2f23b14c580c282a39e065d7aacbfe8 @@ -0,0 +1 @@ +S€gÿ
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fd15b8dd6c27bc65f90e1c988e0245b1ad7d51c5 b/webm_parser/fuzzing/corpus/fd15b8dd6c27bc65f90e1c988e0245b1ad7d51c5 new file mode 100644 index 0000000..0e40a40 --- /dev/null +++ b/webm_parser/fuzzing/corpus/fd15b8dd6c27bc65f90e1c988e0245b1ad7d51c5 @@ -0,0 +1 @@ +S€g™T®k”®’m€b@ŒP5‰Gá€Gâ€ç€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fd608012362d161cc7f3d30e1700c51f4ccef4ac b/webm_parser/fuzzing/corpus/fd608012362d161cc7f3d30e1700c51f4ccef4ac Binary files differnew file mode 100644 index 0000000..601a585 --- /dev/null +++ b/webm_parser/fuzzing/corpus/fd608012362d161cc7f3d30e1700c51f4ccef4ac diff --git a/webm_parser/fuzzing/corpus/fdce5b4f3a038ce7cfeee4deb9a4644edb78189a b/webm_parser/fuzzing/corpus/fdce5b4f3a038ce7cfeee4deb9a4644edb78189a new file mode 100644 index 0000000..5339393 --- /dev/null +++ b/webm_parser/fuzzing/corpus/fdce5b4f3a038ce7cfeee4deb9a4644edb78189a @@ -0,0 +1,3 @@ +Ž +Ž +’’
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fe203731ada762e02bf843b82e33daee4c2efbf5 b/webm_parser/fuzzing/corpus/fe203731ada762e02bf843b82e33daee4c2efbf5 new file mode 100644 index 0000000..dcbe52b --- /dev/null +++ b/webm_parser/fuzzing/corpus/fe203731ada762e02bf843b82e33daee4c2efbf5 @@ -0,0 +1 @@ +S€g—T®k’®m€b@ŠP5‡Gç„Gè
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fe46e6ef4cd5788d89a101c77123387e3fc9d206 b/webm_parser/fuzzing/corpus/fe46e6ef4cd5788d89a101c77123387e3fc9d206 Binary files differnew file mode 100644 index 0000000..a53ee8b --- /dev/null +++ b/webm_parser/fuzzing/corpus/fe46e6ef4cd5788d89a101c77123387e3fc9d206 diff --git a/webm_parser/fuzzing/corpus/fe7ac2ef276b817af3487bab5fe089186ca0484b b/webm_parser/fuzzing/corpus/fe7ac2ef276b817af3487bab5fe089186ca0484b new file mode 100644 index 0000000..da2a10f --- /dev/null +++ b/webm_parser/fuzzing/corpus/fe7ac2ef276b817af3487bab5fe089186ca0484b @@ -0,0 +1 @@ +S€g‹M›t†M»ƒS¬€
\ No newline at end of file diff --git a/webm_parser/fuzzing/corpus/fe7ef7c0e835873b7b1cd780f248114f18adf13a b/webm_parser/fuzzing/corpus/fe7ef7c0e835873b7b1cd780f248114f18adf13a Binary files differnew file mode 100644 index 0000000..04e205c --- /dev/null +++ b/webm_parser/fuzzing/corpus/fe7ef7c0e835873b7b1cd780f248114f18adf13a diff --git a/webm_parser/fuzzing/corpus/ff5274cad94d590347d6cdba7637078f82e3d44a b/webm_parser/fuzzing/corpus/ff5274cad94d590347d6cdba7637078f82e3d44a Binary files differnew file mode 100644 index 0000000..853a484 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ff5274cad94d590347d6cdba7637078f82e3d44a diff --git a/webm_parser/fuzzing/corpus/ffff6a92363e0e55a9688d9bc025cd8dea3b50d4 b/webm_parser/fuzzing/corpus/ffff6a92363e0e55a9688d9bc025cd8dea3b50d4 new file mode 100644 index 0000000..975d9b3 --- /dev/null +++ b/webm_parser/fuzzing/corpus/ffff6a92363e0e55a9688d9bc025cd8dea3b50d4 @@ -0,0 +1 @@ +S€gTÃgŠss‡gÈ„D…!
\ No newline at end of file diff --git a/webm_parser/fuzzing/webm.dict b/webm_parser/fuzzing/webm.dict new file mode 100644 index 0000000..7660ce8 --- /dev/null +++ b/webm_parser/fuzzing/webm.dict @@ -0,0 +1,152 @@ +# Element IDs. +IdEbml = "\x1A\x45\xDF\xA3" +IdEbmlVersion = "\x42\x86" +IdEbmlReadVersion = "\x42\xF7" +IdEbmlMaxIdLength = "\x42\xF2" +IdEbmlMaxSizeLength = "\x42\xF3" +IdDocType = "\x42\x82" +IdDocTypeVersion = "\x42\x87" +IdDocTypeReadVersion = "\x42\x85" +IdVoid = "\xEC" +IdSegment = "\x18\x53\x80\x67" +IdSeekHead = "\x11\x4D\x9B\x74" +IdSeek = "\x4D\xBB" +IdSeekId = "\x53\xAB" +IdSeekPosition = "\x53\xAC" +IdInfo = "\x15\x49\xA9\x66" +IdTimecodeScale = "\x2A\xD7\xB1" +IdDuration = "\x44\x89" +IdDateUtc = "\x44\x61" +IdTitle = "\x7B\xA9" +IdMuxingApp = "\x4D\x80" +IdWritingApp = "\x57\x41" +IdCluster = "\x1F\x43\xB6\x75" +IdTimecode = "\xE7" +IdPrevSize = "\xAB" +IdSimpleBlock = "\xA3" +IdBlockGroup = "\xA0" +IdBlock = "\xA1" +IdBlockVirtual = "\xA2" +IdBlockAdditions = "\x75\xA1" +IdBlockMore = "\xA6" +IdBlockAddId = "\xEE" +IdBlockAdditional = "\xA5" +IdBlockDuration = "\x9B" +IdReferenceBlock = "\xFB" +IdDiscardPadding = "\x75\xA2" +IdSlices = "\x8E" +IdTimeSlice = "\xE8" +IdLaceNumber = "\xCC" +IdTracks = "\x16\x54\xAE\x6B" +IdTrackEntry = "\xAE" +IdTrackNumber = "\xD7" +IdTrackUid = "\x73\xC5" +IdTrackType = "\x83" +IdFlagEnabled = "\xB9" +IdFlagDefault = "\x88" +IdFlagForced = "\x55\xAA" +IdFlagLacing = "\x9C" +IdDefaultDuration = "\x23\xE3\x83" +IdName = "\x53\x6E" +IdLanguage = "\x22\xB5\x9C" +IdCodecId = "\x86" +IdCodecPrivate = "\x63\xA2" +IdCodecName = "\x25\x86\x88" +IdCodecDelay = "\x56\xAA" +IdSeekPreRoll = "\x56\xBB" +IdVideo = "\xE0" +IdFlagInterlaced = "\x9A" +IdStereoMode = "\x53\xB8" +IdAlphaMode = "\x53\xC0" +IdPixelWidth = "\xB0" +IdPixelHeight = "\xBA" +IdPixelCropBottom = "\x54\xAA" +IdPixelCropTop = "\x54\xBB" +IdPixelCropLeft = "\x54\xCC" +IdPixelCropRight = "\x54\xDD" +IdDisplayWidth = "\x54\xB0" +IdDisplayHeight = "\x54\xBA" +IdDisplayUnit = "\x54\xB2" +IdAspectRatioType = "\x54\xB3" +IdFrameRate = "\x23\x83\xE3" +IdColour = "\x55\xB0" +IdMatrixCoefficients = "\x55\xB1" +IdBitsPerChannel = "\x55\xB2" +IdChromaSubsamplingHorz = "\x55\xB3" +IdChromaSubsamplingVert = "\x55\xB4" +IdCbSubsamplingHorz = "\x55\xB5" +IdCbSubsamplingVert = "\x55\xB6" +IdChromaSitingHorz = "\x55\xB7" +IdChromaSitingVert = "\x55\xB8" +IdRange = "\x55\xB9" +IdTransferCharacteristics = "\x55\xBA" +IdPrimaries = "\x55\xBB" +IdMaxCll = "\x55\xBC" +IdMaxFall = "\x55\xBD" +IdMasteringMetadata = "\x55\xD0" +IdPrimaryRChromaticityX = "\x55\xD1" +IdPrimaryRChromaticityY = "\x55\xD2" +IdPrimaryGChromaticityX = "\x55\xD3" +IdPrimaryGChromaticityY = "\x55\xD4" +IdPrimaryBChromaticityX = "\x55\xD5" +IdPrimaryBChromaticityY = "\x55\xD6" +IdWhitePointChromaticityX = "\x55\xD7" +IdWhitePointChromaticityY = "\x55\xD8" +IdLuminanceMax = "\x55\xD9" +IdLuminanceMin = "\x55\xDA" +IdProjection = "\x76\x70" +IdProjectionType = "\x76\x71" +IdProjectionPrivate = "\x76\x72" +IdProjectionPoseYaw = "\x76\x73" +IdProjectionPosePitch = "\x76\x74" +IdProjectionPoseRoll = "\x76\x75" +IdAudio = "\xE1" +IdSamplingFrequency = "\xB5" +IdOutputSamplingFrequency = "\x78\xB5" +IdChannels = "\x9F" +IdBitDepth = "\x62\x64" +IdContentEncodings = "\x6D\x80" +IdContentEncoding = "\x62\x40" +IdContentEncodingOrder = "\x50\x31" +IdContentEncodingScope = "\x50\x32" +IdContentEncodingType = "\x50\x33" +IdContentEncryption = "\x50\x35" +IdContentEncAlgo = "\x47\xE1" +IdContentEncKeyId = "\x47\xE2" +IdContentEncAesSettings = "\x47\xE7" +IdAesSettingsCipherMode = "\x47\xE8" +IdCues = "\x1C\x53\xBB\x6B" +IdCuePoint = "\xBB" +IdCueTime = "\xB3" +IdCueTrackPositions = "\xB7" +IdCueTrack = "\xF7" +IdCueClusterPosition = "\xF1" +IdCueRelativePosition = "\xF0" +IdCueDuration = "\xB2" +IdCueBlockNumber = "\x53\x78" +IdChapters = "\x10\x43\xA7\x70" +IdEditionEntry = "\x45\xB9" +IdChapterAtom = "\xB6" +IdChapterUid = "\x73\xC4" +IdChapterStringUid = "\x56\x54" +IdChapterTimeStart = "\x91" +IdChapterTimeEnd = "\x92" +IdChapterDisplay = "\x80" +IdChapString = "\x85" +IdChapLanguage = "\x43\x7C" +IdChapCountry = "\x43\x7E" +IdTags = "\x12\x54\xC3\x67" +IdTag = "\x73\x73" +IdTargets = "\x63\xC0" +IdTargetTypeValue = "\x68\xCA" +IdTargetType = "\x63\xCA" +IdTagTrackUid = "\x63\xC5" +IdSimpleTag = "\x67\xC8" +IdTagName = "\x45\xA3" +IdTagLanguage = "\x44\x7A" +IdTagDefault = "\x44\x84" +IdTagString = "\x44\x87" +IdTagBinary = "\x44\x85" + +# Interesting sizes. +SizeUnknown = "\xFF" diff --git a/webm_parser/fuzzing/webm_fuzzer.cc b/webm_parser/fuzzing/webm_fuzzer.cc new file mode 100644 index 0000000..bfc4f62 --- /dev/null +++ b/webm_parser/fuzzing/webm_fuzzer.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <iostream> +#include <new> +#include <vector> + +#include "webm/buffer_reader.h" +#include "webm/callback.h" +#include "webm/file_reader.h" +#include "webm/reader.h" +#include "webm/status.h" +#include "webm/webm_parser.h" + +using webm::BufferReader; +using webm::Callback; +using webm::FileReader; +using webm::Reader; +using webm::Status; +using webm::WebmParser; + +static int Run(Reader* reader) { + Callback callback; + WebmParser parser; + +#if WEBM_FUZZER_SEEK_FIRST + parser.DidSeek(); +#endif + + Status status(-1); + try { + status = parser.Feed(&callback, reader); + } catch (std::bad_alloc&) { + // Failed allocations are okay. MSan doesn't throw std::bad_alloc, but + // someday it might. + } + + // BufferReader/FileReader should never return either of the following codes, + // which means the parser never should too: + assert(status.code != Status::kWouldBlock); + assert(status.code != Status::kOkPartial); + + // Only the following ranges have status codes defined: + assert((-1031 <= status.code && status.code <= -1025) || + (-3 <= status.code && status.code <= 0)); + + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) { + BufferReader reader(std::vector<std::uint8_t>(data, data + size)); + return Run(&reader); +} + +#if __AFL_COMPILER +int main(int argc, char* argv[]) { + FILE* file = (argc == 2) ? std::fopen(argv[1], "rb") + : std::freopen(nullptr, "rb", stdin); + if (!file) { + std::cerr << "File cannot be opened\n"; + return EXIT_FAILURE; + } + + FileReader reader(file); + return Run(&reader); +} +#endif diff --git a/webm_parser/include/webm/buffer_reader.h b/webm_parser/include/webm/buffer_reader.h new file mode 100644 index 0000000..d479b5c --- /dev/null +++ b/webm_parser/include/webm/buffer_reader.h @@ -0,0 +1,138 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_BUFFER_READER_H_ +#define INCLUDE_WEBM_BUFFER_READER_H_ + +#include <cstddef> +#include <cstdint> +#include <initializer_list> +#include <vector> + +#include "./reader.h" +#include "./status.h" + +/** + \file + A `Reader` implementation that reads from a `std::vector<std::uint8_t>`. + */ + +namespace webm { + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + A simple reader that reads data from a buffer of bytes. + */ +class BufferReader : public Reader { + public: + /** + Constructs a new, empty reader. + */ + BufferReader() = default; + + /** + Constructs a new reader by copying the provided reader into the new reader. + + \param other The source reader to copy. + */ + BufferReader(const BufferReader& other) = default; + + /** + Copies the provided reader into this reader. + + \param other The source reader to copy. May be equal to `*this`, in which + case this is a no-op. + \return `*this`. + */ + BufferReader& operator=(const BufferReader& other) = default; + + /** + Constructs a new reader by moving the provided reader into the new reader. + + \param other The source reader to move. After moving, it will be reset to an + empty stream. + */ + BufferReader(BufferReader&&); + + /** + Moves the provided reader into this reader. + + \param other The source reader to move. After moving, it will be reset to an + empty stream. May be equal to `*this`, in which case this is a no-op. + \return `*this`. + */ + BufferReader& operator=(BufferReader&&); + + /** + Creates a new `BufferReader` populated with the provided bytes. + + \param bytes Bytes that are assigned to the internal buffer and used as the + source which is read from. + */ + BufferReader(std::initializer_list<std::uint8_t> bytes); + + /** + Creates a new `BufferReader` populated with the provided data. + + \param vector A vector of bytes that is copied to the internal buffer and + used as the source which is read from. + */ + explicit BufferReader(const std::vector<std::uint8_t>& vector); + + /** + Creates a new `BufferReader` populated with the provided data. + + \param vector A vector of bytes that is moved to the internal buffer and used + as the source which is read from. + */ + explicit BufferReader(std::vector<std::uint8_t>&& vector); + + /** + Resets the reader to read from the given list of bytes, starting at the + beginning. + + This makes `reader = {1, 2, 3};` effectively equivalent to `reader = + BufferReader({1, 2, 3});`. + + \param bytes Bytes that are assigned to the internal buffer and used as the + source which is read from. + \return `*this`. + */ + BufferReader& operator=(std::initializer_list<std::uint8_t> bytes); + + Status Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) override; + + Status Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) override; + + std::uint64_t Position() const override; + + /** + Gets the total size of the buffer. + */ + std::size_t size() const { return data_.size(); } + + private: + // Stores the byte buffer from which data is read. + std::vector<std::uint8_t> data_; + + // The position of the reader in the byte buffer. + std::size_t pos_ = 0; +}; + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_BUFFER_READER_H_ diff --git a/webm_parser/include/webm/callback.h b/webm_parser/include/webm/callback.h new file mode 100644 index 0000000..f70f1f8 --- /dev/null +++ b/webm_parser/include/webm/callback.h @@ -0,0 +1,363 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_CALLBACK_H_ +#define INCLUDE_WEBM_CALLBACK_H_ + +#include <cstdint> + +#include "./dom_types.h" +#include "./reader.h" +#include "./status.h" + +/** + \file + The main callback type that receives parsing events. + */ + +namespace webm { + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + The action to be performed when parsing an element. + */ +enum class Action { + /** + Read and parse the element. + */ + kRead, + + /** + Skip the element. Skipped elements are not parsed or stored, and the callback + is not given any further notifications regarding the element. + */ + kSkip, +}; + +/** + A callback that receives parsing events. + + Every method that returns a `Status` should return `Status::kOkCompleted` when + the method has completed and parsing should continue. Returning any other value + will cause parsing to stop. Parsing may be resumed if the returned status was + not a parsing error (see `Status::is_parsing_error()`). When parsing is + resumed, the same `Callback` method will be called again. + + Methods that take a `Reader` expect the implementation to consume (either via + `Reader::Read()` or `Reader::Skip()`) the specified number of bytes before + returning `Status::kOkCompleted`. Default implementations will call + `Reader::Skip()` to skip the specified number of bytes and the resulting + `Status` will be returned (unless it's `Status::kOkPartial`, in which case + `Reader::Skip()` will be called again to skip more data). + + Throwing an exception from the member functions is permitted, though if the + exception will be caught and parsing resumed, then the reader should not + advance its position (for methods that take a `Reader`) before the exception is + thrown. When parsing is resumed, the same `Callback` method will be called + again. + + Users should derive from this class and override member methods as needed. + */ +class Callback { + public: + virtual ~Callback() = default; + + /** + Called when the parser starts a new element. This is called after the + elements ID and size has been parsed, but before any of its body has been + read (or validated). + + Defaults to `Action::kRead` and returning `Status::kOkCompleted`. + + \param metadata Metadata about the element that has just been encountered. + \param[out] action The action that should be taken when handling this + element. Will not be null. + */ + virtual Status OnElementBegin(const ElementMetadata& metadata, + Action* action); + + /** + Called when the parser encounters an unknown element. + + Defaults to calling (and returning the result of) `Reader::Skip()`. + + \param metadata Metadata about the element. + \param reader The reader that should be used to consume data. Will not be + null. + \param[in,out] bytes_remaining The number of remaining bytes that need to be + consumed for the element. Will not be null. + \return `Status::kOkCompleted` when the element has been fully consumed and + `bytes_remaining` is now zero. + */ + virtual Status OnUnknownElement(const ElementMetadata& metadata, + Reader* reader, + std::uint64_t* bytes_remaining); + + /** + Called when the parser encounters an `Id::kEbml` element and it has been + fully parsed. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param ebml The parsed element. + */ + virtual Status OnEbml(const ElementMetadata& metadata, const Ebml& ebml); + + /** + Called when the parser encounters an Id::kVoid element. + + Defaults to calling (and returning the result of) Reader::Skip. + + \param metadata Metadata about the element. + \param reader The reader that should be used to consume data. Will not be + null. + \param[in,out] bytes_remaining The number of remaining bytes that need to be + consumed for the element. Will not be null. + \return `Status::kOkCompleted` when the element has been fully consumed and + `bytes_remaining` is now zero. + */ + virtual Status OnVoid(const ElementMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining); + + /** + Called when the parser starts an `Id::kSegment` element. + + Defaults to `Action::kRead` and returning `Status::kOkCompleted`. + + \param metadata Metadata about the element that has just been encountered. + \param[out] action The action that should be taken when handling this + element. Will not be null. + */ + virtual Status OnSegmentBegin(const ElementMetadata& metadata, + Action* action); + + /** + Called when the parser encounters an `Id::kSeek` element and it has been + fully parsed. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param seek The parsed element. + */ + virtual Status OnSeek(const ElementMetadata& metadata, const Seek& seek); + + /** + Called when the parser encounters an `Id::kInfo` element and it has been + fully parsed. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param info The parsed element. + */ + virtual Status OnInfo(const ElementMetadata& metadata, const Info& info); + + /** + Called when the parser starts an `Id::kCluster` element. + + Because Cluster elements should start with a Timecode (and optionally + PrevSize) child, this method is not invoked until a child BlockGroup or + SimpleBlock element is encountered (or the Cluster ends if no such child + exists). + + Defaults to `Action::kRead` and returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param cluster The element, as it has currently been parsed. + \param[out] action The action that should be taken when handling this + element. Will not be null. + */ + virtual Status OnClusterBegin(const ElementMetadata& metadata, + const Cluster& cluster, Action* action); + + /** + Called when the parser starts an `Id::kSimpleBlock` element. + + Defaults to `Action::kRead` and returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param simple_block The parsed SimpleBlock header. + \param[out] action The action that should be taken when handling this + element. Will not be null. + */ + virtual Status OnSimpleBlockBegin(const ElementMetadata& metadata, + const SimpleBlock& simple_block, + Action* action); + + /** + Called when the parser finishes an `Id::kSimpleBlock` element. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param simple_block The parsed SimpleBlock header. + */ + virtual Status OnSimpleBlockEnd(const ElementMetadata& metadata, + const SimpleBlock& simple_block); + + /** + Called when the parser starts an `Id::kBlockGroup` element. + + Defaults to `Action::kRead` and returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param[out] action The action that should be taken when handling this + element. Will not be null. + */ + virtual Status OnBlockGroupBegin(const ElementMetadata& metadata, + Action* action); + + /** + Called when the parser starts an `Id::kBlock` element. + + Defaults to `Action::kRead` and returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param block The parsed Block header. + \param[out] action The action that should be taken when handling this + element. Will not be null. + */ + virtual Status OnBlockBegin(const ElementMetadata& metadata, + const Block& block, Action* action); + + /** + Called when the parser finishes an `Id::Block` element. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param block The parsed Block header. + */ + virtual Status OnBlockEnd(const ElementMetadata& metadata, + const Block& block); + + /** + Called when the parser finishes an `Id::kBlockGroup` element. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param block_group The parsed element. + */ + virtual Status OnBlockGroupEnd(const ElementMetadata& metadata, + const BlockGroup& block_group); + + /** + Called when the parser encounters a frame within a `Id::kBlock` or + `Id::kSimpleBlock` element. + + Defaults to calling (and returning the result of) `Reader::Skip`. + + \param metadata Metadata about the frame. + \param reader The reader that should be used to consume data. Will not be + null. + \param[in,out] bytes_remaining The number of remaining bytes that need to be + consumed for the frame. Will not be null. + \return `Status::kOkCompleted` when the frame has been fully consumed and + `bytes_remaining` is now zero. + */ + virtual Status OnFrame(const FrameMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining); + + /** + Called when the parser finishes an `Id::kCluster` element. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param cluster The parsed element. + */ + virtual Status OnClusterEnd(const ElementMetadata& metadata, + const Cluster& cluster); + + /** + Called when the parser starts an `Id::kTrackEntry` element. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param track_entry The parsed element. + */ + virtual Status OnTrackEntry(const ElementMetadata& metadata, + const TrackEntry& track_entry); + + /** + Called when the parser encounters an `Id::kCuePoint` element and it has been + fully parsed. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param cue_point The parsed element. + */ + virtual Status OnCuePoint(const ElementMetadata& metadata, + const CuePoint& cue_point); + + /** + Called when the parser encounters an `Id::kEditionEntry` element and it has + been fully parsed. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param edition_entry The parsed element. + */ + virtual Status OnEditionEntry(const ElementMetadata& metadata, + const EditionEntry& edition_entry); + + /** + Called when the parser encounters an `Id::kTag` element and it has been fully + parsed. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + \param tag The parsed element. + */ + virtual Status OnTag(const ElementMetadata& metadata, const Tag& tag); + + /** + Called when the parser finishes an `Id::kSegment` element. + + Defaults to returning `Status::kOkCompleted`. + + \param metadata Metadata about the element. + */ + virtual Status OnSegmentEnd(const ElementMetadata& metadata); + + protected: + /** + Calls (and returns the result of) `Reader::Skip()`, skipping (up to) the + requested number of bytes. + + Unlike `Reader::Skip()`, this method may be called with `*bytes_remaining == + 0`, which will result in `Status::kOkCompleted`. `Reader::Skip()` will be + called multiple times if it returns `Status::kOkPartial` until it returns a + different status (indicating the requested number of bytes has been fully + skipped or some error occurred). + + \param reader The reader that should be used to skip data. Must not be null. + \param[in,out] bytes_remaining The number of remaining bytes that need to be + skipped. Must not be null. May be zero. + \return The result of `Reader::Skip()`. + */ + static Status Skip(Reader* reader, std::uint64_t* bytes_remaining); +}; + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_CALLBACK_H_ diff --git a/webm_parser/include/webm/dom_types.h b/webm_parser/include/webm/dom_types.h new file mode 100644 index 0000000..7ae4a0c --- /dev/null +++ b/webm_parser/include/webm/dom_types.h @@ -0,0 +1,1781 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_DOM_TYPES_H_ +#define INCLUDE_WEBM_DOM_TYPES_H_ + +#include <cstdint> +#include <string> +#include <vector> + +#include "./element.h" +#include "./id.h" + +/** + \file + Data structures representing parsed DOM objects. + + For more information on each type and member, see the WebM specification for + the element that each type/member represents. + */ + +namespace webm { + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + Metadata for a single frame. + */ +struct FrameMetadata { + /** + Metadata for the EBML element (\WebMID{Block} or \WebMID{SimpleBlock}) that + contains this frame. + */ + ElementMetadata parent_element; + + /** + Absolute byte position (from the beginning of the byte stream/file) of the + frame start. + */ + std::uint64_t position; + + /** + Size (in bytes) of the frame. + */ + std::uint64_t size; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const FrameMetadata& other) const { + return parent_element == other.parent_element && + position == other.position && size == other.size; + } +}; + +/** + A parsed \WebMID{BlockMore} element. + */ +struct BlockMore { + /** + A parsed \WebMID{BlockAddID} element. + */ + Element<std::uint64_t> id{1}; + + /** + A parsed \WebMID{BlockAdditional} element. + */ + Element<std::vector<std::uint8_t>> data; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const BlockMore& other) const { + return id == other.id && data == other.data; + } +}; + +/** + A parsed \WebMID{BlockAdditions} element. + */ +struct BlockAdditions { + /** + Parsed \WebMID{BlockMore} elements. + */ + std::vector<Element<BlockMore>> block_mores; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const BlockAdditions& other) const { + return block_mores == other.block_mores; + } +}; + +/** + A parsed \WebMID{TimeSlice} element (deprecated). + */ +struct TimeSlice { + /** + A parsed \WebMID{LaceNumber} element (deprecated). + */ + Element<std::uint64_t> lace_number; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const TimeSlice& other) const { + return lace_number == other.lace_number; + } +}; + +/** + A parsed \WebMID{Slices} element (deprecated). + */ +struct Slices { + /** + Parsed \WebMID{TimeSlice} elements (deprecated). + */ + std::vector<Element<TimeSlice>> slices; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Slices& other) const { return slices == other.slices; } +}; + +/** + A parsed \WebMID{BlockVirtual} element. + */ +struct VirtualBlock { + /** + The virtual block's track number. + */ + std::uint64_t track_number; + + /** + The timecode of the virtual block. + */ + std::int16_t timecode; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const VirtualBlock& other) const { + return track_number == other.track_number && timecode == other.timecode; + } +}; + +/** + The frame lacing used in a block. + */ +enum class Lacing : std::uint8_t { + /** + No lacing is used. + */ + kNone = 0x00, + + /** + Xiph-style lacing is used. + */ + kXiph = 0x02, + + /** + Fixed-lacing is used, where each frame has the same, fixed size. + */ + kFixed = 0x04, + + /** + EBML-style lacing is used. + */ + kEbml = 0x06, +}; + +/** + A parsed \WebMID{Block} element. + */ +struct Block { + /** + The block's track number. + */ + std::uint64_t track_number; + + /** + The number of frames in the block. + */ + int num_frames; + + /** + The timecode of the block (relative to the containing \WebMID{Cluster}'s + timecode). + */ + std::int16_t timecode; + + /** + The lacing used to store frames in the block. + */ + Lacing lacing; + + /** + True if the frames are visible, false if they are invisible. + */ + bool is_visible; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Block& other) const { + return track_number == other.track_number && + num_frames == other.num_frames && timecode == other.timecode && + lacing == other.lacing && is_visible == other.is_visible; + } +}; + +/** + A parsed \WebMID{SimpleBlock} element. + */ +struct SimpleBlock : public Block { + /** + True if the frames are all key frames. + */ + bool is_key_frame; + + /** + True if frames can be discarded during playback if needed. + */ + bool is_discardable; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const SimpleBlock& other) const { + return Block::operator==(other) && is_key_frame == other.is_key_frame && + is_discardable == other.is_discardable; + } +}; + +/** + A parsed \WebMID{BlockGroup} element. + */ +struct BlockGroup { + /** + A parsed \WebMID{Block} element. + */ + Element<Block> block; + + /** + A parsed \WebMID{BlockVirtual} element. + */ + Element<VirtualBlock> virtual_block; + + /** + A parsed \WebMID{BlockAdditions} element. + */ + Element<BlockAdditions> additions; + + /** + A parsed \WebMID{BlockDuration} element. + */ + Element<std::uint64_t> duration; + + /** + Parsed \WebMID{ReferenceBlock} elements. + */ + std::vector<Element<std::int64_t>> references; + + /** + A parsed \WebMID{DiscardPadding} element. + */ + Element<std::int64_t> discard_padding; + + /** + A parsed \WebMID{Slices} element (deprecated). + */ + Element<Slices> slices; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const BlockGroup& other) const { + return block == other.block && virtual_block == other.virtual_block && + additions == other.additions && duration == other.duration && + references == other.references && + discard_padding == other.discard_padding && slices == other.slices; + } +}; + +/** + A parsed \WebMID{Cluster} element. + */ +struct Cluster { + /** + A parsed \WebMID{Timecode} element. + */ + Element<std::uint64_t> timecode; + + /** + A parsed \WebMID{PrevSize} element. + */ + Element<std::uint64_t> previous_size; + + /** + Parsed \WebMID{SimpleBlock} elements. + */ + std::vector<Element<SimpleBlock>> simple_blocks; + + /** + Parsed \WebMID{BlockGroup} elements. + */ + std::vector<Element<BlockGroup>> block_groups; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Cluster& other) const { + return timecode == other.timecode && previous_size == other.previous_size && + simple_blocks == other.simple_blocks && + block_groups == other.block_groups; + } +}; + +/** + A parsed \WebMID{EBML} element. + */ +struct Ebml { + /** + A parsed \WebMID{EBMLVersion} element. + */ + Element<std::uint64_t> ebml_version{1}; + + /** + A parsed \WebMID{EBMLReadVersion} element. + */ + Element<std::uint64_t> ebml_read_version{1}; + + /** + A parsed \WebMID{EBMLMaxIDLength} element. + */ + Element<std::uint64_t> ebml_max_id_length{4}; + + /** + A parsed \WebMID{EBMLMaxSizeLength} element. + */ + Element<std::uint64_t> ebml_max_size_length{8}; + + /** + A parsed \WebMID{DocType} element. + */ + Element<std::string> doc_type{"matroska"}; + + /** + A parsed \WebMID{DocTypeVersion} element. + */ + Element<std::uint64_t> doc_type_version{1}; + + /** + A parsed \WebMID{DocTypeReadVersion} element. + */ + Element<std::uint64_t> doc_type_read_version{1}; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Ebml& other) const { + return ebml_version == other.ebml_version && + ebml_read_version == other.ebml_read_version && + ebml_max_id_length == other.ebml_max_id_length && + ebml_max_size_length == other.ebml_max_size_length && + doc_type == other.doc_type && + doc_type_version == other.doc_type_version && + doc_type_read_version == other.doc_type_read_version; + } +}; + +/** + A parsed \WebMID{Info} element. + */ +struct Info { + /** + A parsed \WebMID{TimecodeScale} element. + */ + Element<std::uint64_t> timecode_scale{1000000}; + + /** + A parsed \WebMID{Duration} element. + */ + Element<double> duration; + + /** + A parsed \WebMID{DateUTC} element. + */ + Element<std::int64_t> date_utc; + + /** + A parsed \WebMID{Title} element. + */ + Element<std::string> title; + + /** + A parsed \WebMID{MuxingApp} element. + */ + Element<std::string> muxing_app; + + /** + A parsed \WebMID{WritingApp} element. + */ + Element<std::string> writing_app; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Info& other) const { + return timecode_scale == other.timecode_scale && + duration == other.duration && date_utc == other.date_utc && + title == other.title && muxing_app == other.muxing_app && + writing_app == other.writing_app; + } +}; + +/** + A parsed \WebMID{Seek} element. + */ +struct Seek { + /** + A parsed \WebMID{SeekID} element. + */ + Element<Id> id; + + /** + A parsed \WebMID{SeekPosition} element. + */ + Element<std::uint64_t> position; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Seek& other) const { + return id == other.id && position == other.position; + } +}; + +/** + A parsed \WebMID{Audio} element. + */ +struct Audio { + /** + A parsed \WebMID{SamplingFrequency} element. + */ + Element<double> sampling_frequency{8000}; + + /** + A parsed \WebMID{OutputSamplingFrequency} element. + */ + Element<double> output_frequency{8000}; + + /** + A parsed \WebMID{Channels} element. + */ + Element<std::uint64_t> channels{1}; + + /** + A parsed \WebMID{BitDepth} element. + */ + Element<std::uint64_t> bit_depth; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Audio& other) const { + return sampling_frequency == other.sampling_frequency && + output_frequency == other.output_frequency && + channels == other.channels && bit_depth == other.bit_depth; + } +}; + +/** + A parsed \WebMID{MasteringMetadata} element. + */ +struct MasteringMetadata { + /** + A parsed \WebMID{PrimaryRChromaticityX} element. + */ + Element<double> primary_r_chromaticity_x; + + /** + A parsed \WebMID{PrimaryRChromaticityY} element. + */ + Element<double> primary_r_chromaticity_y; + + /** + A parsed \WebMID{PrimaryGChromaticityX} element. + */ + Element<double> primary_g_chromaticity_x; + + /** + A parsed \WebMID{PrimaryGChromaticityY} element. + */ + Element<double> primary_g_chromaticity_y; + + /** + A parsed \WebMID{PrimaryBChromaticityX} element. + */ + Element<double> primary_b_chromaticity_x; + + /** + A parsed \WebMID{PrimaryBChromaticityY} element. + */ + Element<double> primary_b_chromaticity_y; + + /** + A parsed \WebMID{WhitePointChromaticityX} element. + */ + Element<double> white_point_chromaticity_x; + + /** + A parsed \WebMID{WhitePointChromaticityY} element. + */ + Element<double> white_point_chromaticity_y; + + /** + A parsed \WebMID{LuminanceMax} element. + */ + Element<double> luminance_max; + + /** + A parsed \WebMID{LuminanceMin} element. + */ + Element<double> luminance_min; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const MasteringMetadata& other) const { + return primary_r_chromaticity_x == other.primary_r_chromaticity_x && + primary_r_chromaticity_y == other.primary_r_chromaticity_y && + primary_g_chromaticity_x == other.primary_g_chromaticity_x && + primary_g_chromaticity_y == other.primary_g_chromaticity_y && + primary_b_chromaticity_x == other.primary_b_chromaticity_x && + primary_b_chromaticity_y == other.primary_b_chromaticity_y && + white_point_chromaticity_x == other.white_point_chromaticity_x && + white_point_chromaticity_y == other.white_point_chromaticity_y && + luminance_max == other.luminance_max && + luminance_min == other.luminance_min; + } +}; + +/** + A parsed \WebMID{MatrixCoefficients} element. + + Matroska/WebM adopted these values from Table 4 of ISO/IEC 23001-8:2013/DCOR1. + See that document for further details. + */ +enum class MatrixCoefficients : std::uint64_t { + /** + The identity matrix. + + Typically used for GBR (often referred to as RGB); however, may also be used + for YZX (often referred to as XYZ). + */ + kRgb = 0, + + /** + Rec. ITU-R BT.709-5. + */ + kBt709 = 1, + + /** + Image characteristics are unknown or are determined by the application. + */ + kUnspecified = 2, + + /** + United States Federal Communications Commission Title 47 Code of Federal + Regulations (2003) 73.682 (a) (20). + */ + kFcc = 4, + + /** + Rec. ITU-R BT.470‑6 System B, G (historical). + */ + kBt470Bg = 5, + + /** + Society of Motion Picture and Television Engineers 170M (2004). + */ + kSmpte170M = 6, + + /** + Society of Motion Picture and Television Engineers 240M (1999). + */ + kSmpte240M = 7, + + /** + YCgCo. + */ + kYCgCo = 8, + + /** + Rec. ITU-R BT.2020 (non-constant luminance). + */ + kBt2020NonconstantLuminance = 9, + + /** + Rec. ITU-R BT.2020 (constant luminance). + */ + kBt2020ConstantLuminance = 10, +}; + +/** + A parsed \WebMID{Range} element. + */ +enum class Range : std::uint64_t { + /** + Unspecified. + */ + kUnspecified = 0, + + /** + Broadcast range. + */ + kBroadcast = 1, + + /** + Full range (no clipping). + */ + kFull = 2, + + /** + Defined by MatrixCoefficients/TransferCharacteristics. + */ + kDerived = 3, +}; + +/** + A parsed \WebMID{TransferCharacteristics} element. + + Matroska/WebM adopted these values from Table 3 of ISO/IEC 23001-8:2013/DCOR1. + See that document for further details. + */ +enum class TransferCharacteristics : std::uint64_t { + /** + Rec. ITU-R BT.709-6. + */ + kBt709 = 1, + + /** + Image characteristics are unknown or are determined by the application. + */ + kUnspecified = 2, + + /** + Rec. ITU‑R BT.470‑6 System M (historical) with assumed display gamma 2.2. + */ + kGamma22curve = 4, + + /** + Rec. ITU‑R BT.470-6 System B, G (historical) with assumed display gamma 2.8. + */ + kGamma28curve = 5, + + /** + Society of Motion Picture and Television Engineers 170M (2004). + */ + kSmpte170M = 6, + + /** + Society of Motion Picture and Television Engineers 240M (1999). + */ + kSmpte240M = 7, + + /** + Linear transfer characteristics. + */ + kLinear = 8, + + /** + Logarithmic transfer characteristic (100:1 range). + */ + kLog = 9, + + /** + Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range). + */ + kLogSqrt = 10, + + /** + IEC 61966-2-4. + */ + kIec6196624 = 11, + + /** + Rec. ITU‑R BT.1361-0 extended colour gamut system (historical). + */ + kBt1361ExtendedColourGamut = 12, + + /** + IEC 61966-2-1 sRGB or sYCC. + */ + kIec6196621 = 13, + + /** + Rec. ITU-R BT.2020-2 (10-bit system). + */ + k10BitBt2020 = 14, + + /** + Rec. ITU-R BT.2020-2 (12-bit system). + */ + k12BitBt2020 = 15, + + /** + Society of Motion Picture and Television Engineers ST 2084. + */ + kSmpteSt2084 = 16, + + /** + Society of Motion Picture and Television Engineers ST 428-1. + */ + kSmpteSt4281 = 17, + + /** + Association of Radio Industries and Businesses (ARIB) STD-B67. + */ + kAribStdB67Hlg = 18, +}; + +/** + A parsed \WebMID{Primaries} element. + + Matroska/WebM adopted these values from Table 2 of ISO/IEC 23001-8:2013/DCOR1. + See that document for further details. + */ +enum class Primaries : std::uint64_t { + /** + Rec. ITU‑R BT.709-6. + */ + kBt709 = 1, + + /** + Image characteristics are unknown or are determined by the application. + */ + kUnspecified = 2, + + /** + Rec. ITU‑R BT.470‑6 System M (historical). + */ + kBt470M = 4, + + /** + Rec. ITU‑R BT.470‑6 System B, G (historical). + */ + kBt470Bg = 5, + + /** + Society of Motion Picture and Television Engineers 170M (2004). + */ + kSmpte170M = 6, + + /** + Society of Motion Picture and Television Engineers 240M (1999). + */ + kSmpte240M = 7, + + /** + Generic film. + */ + kFilm = 8, + + /** + Rec. ITU-R BT.2020-2. + */ + kBt2020 = 9, + + /** + Society of Motion Picture and Television Engineers ST 428-1. + */ + kSmpteSt4281 = 10, + + /** + Society of Motion Picture and Television Engineers RP 431-2 (a.k.a. DCI-P3). + */ + kSmpteRp431 = 11, + + /** + Society of Motion Picture and Television Engineers EG 432-1 + (a.k.a. DCI-P3 D65). + */ + kSmpteEg432 = 12, + + /** + JEDEC P22 phosphors/EBU Tech. 3213-E (1975). + */ + kJedecP22Phosphors = 22, +}; + +/** + A parsed \WebMID{Colour} element. + */ +struct Colour { + /** + A parsed \WebMID{MatrixCoefficients} element. + */ + Element<MatrixCoefficients> matrix_coefficients{ + MatrixCoefficients::kUnspecified}; + + /** + A parsed \WebMID{BitsPerChannel} element. + */ + Element<std::uint64_t> bits_per_channel{0}; + + /** + A parsed \WebMID{ChromaSubsamplingHorz} element. + */ + Element<std::uint64_t> chroma_subsampling_x; + + /** + A parsed \WebMID{ChromaSubsamplingVert} element. + */ + Element<std::uint64_t> chroma_subsampling_y; + + /** + A parsed \WebMID{CbSubsamplingHorz} element. + */ + Element<std::uint64_t> cb_subsampling_x; + + /** + A parsed \WebMID{CbSubsamplingVert} element. + */ + Element<std::uint64_t> cb_subsampling_y; + + /** + A parsed \WebMID{ChromaSitingHorz} element. + */ + Element<std::uint64_t> chroma_siting_x{0}; + + /** + A parsed \WebMID{ChromaSitingVert} element. + */ + Element<std::uint64_t> chroma_siting_y{0}; + + /** + A parsed \WebMID{Range} element. + */ + Element<Range> range{Range::kUnspecified}; + + /** + A parsed \WebMID{TransferCharacteristics} element. + */ + Element<TransferCharacteristics> transfer_characteristics{ + TransferCharacteristics::kUnspecified}; + + /** + A parsed \WebMID{Primaries} element. + */ + Element<Primaries> primaries{Primaries::kUnspecified}; + + /** + A parsed \WebMID{MaxCLL} element. + */ + Element<std::uint64_t> max_cll; + + /** + A parsed \WebMID{MaxFALL} element. + */ + Element<std::uint64_t> max_fall; + + /** + A parsed \WebMID{MasteringMetadata} element. + */ + Element<MasteringMetadata> mastering_metadata; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Colour& other) const { + return matrix_coefficients == other.matrix_coefficients && + bits_per_channel == other.bits_per_channel && + chroma_subsampling_x == other.chroma_subsampling_x && + chroma_subsampling_y == other.chroma_subsampling_y && + cb_subsampling_x == other.cb_subsampling_x && + cb_subsampling_y == other.cb_subsampling_y && + chroma_siting_x == other.chroma_siting_x && + chroma_siting_y == other.chroma_siting_y && range == other.range && + transfer_characteristics == other.transfer_characteristics && + primaries == other.primaries && max_cll == other.max_cll && + max_fall == other.max_fall && + mastering_metadata == other.mastering_metadata; + } +}; + +/** + A parsed \WebMID{ProjectionType} element. + */ +enum class ProjectionType : std::uint64_t { + /** + Rectangular. + */ + kRectangular = 0, + + /** + Equirectangular. + */ + kEquirectangular = 1, + + /** + Cube map. + */ + kCubeMap = 2, + + /** + Mesh. + */ + kMesh = 3, +}; + +/** + A parsed \WebMID{Projection} element. + */ +struct Projection { + /** + A parsed \WebMID{ProjectionType} element. + */ + Element<ProjectionType> type; + + /** + A parsed \WebMID{ProjectionPrivate} element. + */ + Element<std::vector<std::uint8_t>> projection_private; + + /** + A parsed \WebMID{ProjectionPoseYaw} element. + */ + Element<double> pose_yaw; + + /** + A parsed \WebMID{ProjectionPosePitch} element. + */ + Element<double> pose_pitch; + + /** + A parsed \WebMID{ProjectionPoseRoll} element. + */ + Element<double> pose_roll; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Projection& other) const { + return type == other.type && + projection_private == other.projection_private && + pose_yaw == other.pose_yaw && pose_pitch == other.pose_pitch && + pose_roll == other.pose_roll; + } +}; + +/** + A parsed \WebMID{FlagInterlaced} element. + */ +enum class FlagInterlaced : std::uint64_t { + /** + Unspecified. + */ + kUnspecified = 0, + + /** + Interlaced. + */ + kInterlaced = 1, + + /** + Progressive. + */ + kProgressive = 2, +}; + +/** + A parsed \WebMID{StereoMode} element. + */ +enum class StereoMode : std::uint64_t { + /** + Mono. + */ + kMono = 0, + + /** + Side-by-side, left eye is first. + */ + kSideBySideLeftFirst = 1, + + /** + Top-bottom, right eye is first. + */ + kTopBottomRightFirst = 2, + + /** + Top-bottom, left eye is first. + */ + kTopBottomLeftFirst = 3, + + /** + Checkboard, right eye is first. + */ + kCheckboardRightFirst = 4, + + /** + Checkboard, left eye is first. + */ + kCheckboardLeftFirst = 5, + + /** + Row interleaved, right eye is first. + */ + kRowInterleavedRightFirst = 6, + + /** + Row interleaved, left eye is first. + */ + kRowInterleavedLeftFirst = 7, + + /** + Column interleaved, right eye is first. + */ + kColumnInterleavedRightFirst = 8, + + /** + Column interleaved, left eye is first. + */ + kColumnInterleavedLeftFirst = 9, + + /** + Anaglyph (cyan/read). + */ + kAnaglyphCyanRed = 10, + + /** + Side-by-side, right eye is first. + */ + kSideBySideRightFirst = 11, + + /** + Anaglyph (green/magenta). + */ + kAnaglyphGreenMagenta = 12, + + /** + Both eyes are laced in one block, left eye is first. + */ + kBlockLacedLeftFirst = 13, + + /** + Both eyes are laced in one block, right eye is first. + */ + kBlockLacedRightFirst = 14, + + /** + Stereo, but the layout for the left and right eyes is application dependent + and must be determined from other data (like the ProjectionPrivate element). + */ + kStereoCustom = 15, +}; + +/** + A parsed \WebMID{DisplayUnit} element. + + Note that WebM only supports pixel display units. Centimeters, inches, and + display aspect ratio aren't supported. + */ +enum class DisplayUnit : std::uint64_t { + // The only value legal value in WebM is 0 (pixels). + /** + Pixels. + + This is the only option supported by WebM. + */ + kPixels = 0, + + /** + Centimeters. + */ + kCentimeters = 1, + + /** + Inches. + */ + kInches = 2, + + /** + Display aspect ratio. + */ + kDisplayAspectRatio = 3, +}; + +/** + A parsed \WebMID{AspectRatioType} element. + */ +enum class AspectRatioType : std::uint64_t { + /** + Free resizing. + */ + kFreeResizing = 0, + + /** + Keep aspect ratio. + */ + kKeep = 1, + + /** + Fixed aspect ratio. + */ + kFixed = 2, +}; + +/** + A parsed \WebMID{Video} element. + */ +struct Video { + /** + A parsed \WebMID{FlagInterlaced} element. + */ + Element<FlagInterlaced> interlaced{FlagInterlaced::kUnspecified}; + + /** + A parsed \WebMID{StereoMode} element. + */ + Element<StereoMode> stereo_mode{StereoMode::kMono}; + + /** + A parsed \WebMID{AlphaMode} element. + */ + Element<std::uint64_t> alpha_mode{0}; + + /** + A parsed \WebMID{PixelWidth} element. + */ + Element<std::uint64_t> pixel_width; + + /** + A parsed \WebMID{PixelHeight} element. + */ + Element<std::uint64_t> pixel_height; + + /** + A parsed \WebMID{PixelCropBottom} element. + */ + Element<std::uint64_t> pixel_crop_bottom{0}; + + /** + A parsed \WebMID{PixelCropTop} element. + */ + Element<std::uint64_t> pixel_crop_top{0}; + + /** + A parsed \WebMID{PixelCropLeft} element. + */ + Element<std::uint64_t> pixel_crop_left{0}; + + /** + A parsed \WebMID{PixelCropRight} element. + */ + Element<std::uint64_t> pixel_crop_right{0}; + + /** + A parsed \WebMID{DisplayWidth} element. + */ + Element<std::uint64_t> display_width; + + /** + A parsed \WebMID{DisplayHeight} element. + */ + Element<std::uint64_t> display_height; + + /** + A parsed \WebMID{DisplayUnit} element. + */ + Element<DisplayUnit> display_unit{DisplayUnit::kPixels}; + + /** + A parsed \WebMID{AspectRatioType} element. + */ + Element<AspectRatioType> aspect_ratio_type{AspectRatioType::kFreeResizing}; + + /** + A parsed \WebMID{FrameRate} element (deprecated). + */ + Element<double> frame_rate; + + /** + A parsed \WebMID{Colour} element. + */ + Element<Colour> colour; + + /** + A parsed \WebMID{Projection} element. + */ + Element<Projection> projection; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Video& other) const { + return interlaced == other.interlaced && stereo_mode == other.stereo_mode && + alpha_mode == other.alpha_mode && pixel_width == other.pixel_width && + pixel_height == other.pixel_height && + pixel_crop_bottom == other.pixel_crop_bottom && + pixel_crop_top == other.pixel_crop_top && + pixel_crop_left == other.pixel_crop_left && + pixel_crop_right == other.pixel_crop_right && + display_width == other.display_width && + display_height == other.display_height && + display_unit == other.display_unit && + aspect_ratio_type == other.aspect_ratio_type && + frame_rate == other.frame_rate && colour == other.colour && + projection == other.projection; + } +}; + +/** + A parsed \WebMID{AESSettingsCipherMode} element. + */ +enum class AesSettingsCipherMode : std::uint64_t { + /** + Counter (CTR). + */ + kCtr = 1, +}; + +/** + A parsed \WebMID{ContentEncAESSettings} element. + */ +struct ContentEncAesSettings { + /** + A parsed \WebMID{AESSettingsCipherMode} element. + */ + Element<AesSettingsCipherMode> aes_settings_cipher_mode{ + AesSettingsCipherMode::kCtr}; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const ContentEncAesSettings& other) const { + return aes_settings_cipher_mode == other.aes_settings_cipher_mode; + } +}; + +/** + A parsed \WebMID{ContentEncAlgo} element. + */ +enum class ContentEncAlgo : std::uint64_t { + /** + The contents have been signed but not encrypted. + */ + kOnlySigned = 0, + + /** + DES encryption. + */ + kDes = 1, + + /** + 3DES encryption. + */ + k3Des = 2, + + /** + Twofish encryption. + */ + kTwofish = 3, + + /** + Blowfish encryption. + */ + kBlowfish = 4, + + /** + AES encryption. + */ + kAes = 5, +}; + +/** + A parsed \WebMID{ContentEncryption} element. + */ +struct ContentEncryption { + /** + A parsed \WebMID{ContentEncAlgo} element. + */ + Element<ContentEncAlgo> algorithm{ContentEncAlgo::kOnlySigned}; + + /** + A parsed \WebMID{ContentEncKeyID} element. + */ + Element<std::vector<std::uint8_t>> key_id; + + /** + A parsed \WebMID{ContentEncAESSettings} element. + */ + Element<ContentEncAesSettings> aes_settings; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const ContentEncryption& other) const { + return algorithm == other.algorithm && key_id == other.key_id && + aes_settings == other.aes_settings; + } +}; + +/** + A parsed \WebMID{ContentEncodingType} element. + */ +enum class ContentEncodingType : std::uint64_t { + /** + Compression. + */ + kCompression = 0, + + /** + Encryption. + */ + kEncryption = 1, +}; + +/** + A parsed \WebMID{ContentEncoding} element. + */ +struct ContentEncoding { + /** + A parsed \WebMID{ContentEncodingOrder} element. + */ + Element<std::uint64_t> order{0}; + + /** + A parsed \WebMID{ContentEncodingScope} element. + */ + Element<std::uint64_t> scope{1}; + + /** + A parsed \WebMID{ContentEncodingType} element. + */ + Element<ContentEncodingType> type{ContentEncodingType::kCompression}; + + /** + A parsed \WebMID{ContentEncryption} element. + */ + Element<ContentEncryption> encryption; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const ContentEncoding& other) const { + return order == other.order && scope == other.scope && type == other.type && + encryption == other.encryption; + } +}; + +/** + A parsed \WebMID{ContentEncodings} element. + */ +struct ContentEncodings { + /** + Parsed \WebMID{ContentEncoding} elements. + */ + std::vector<Element<ContentEncoding>> encodings; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const ContentEncodings& other) const { + return encodings == other.encodings; + } +}; + +/** + A parsed \WebMID{TrackType} element. + */ +enum class TrackType : std::uint64_t { + /** + Video. + */ + kVideo = 0x01, + + /** + Audio. + */ + kAudio = 0x02, + + /** + Complex. + */ + kComplex = 0x03, + + /** + Logo. + */ + kLogo = 0x10, + + /** + Subtitle. + */ + kSubtitle = 0x11, + + /** + Buttons. + */ + kButtons = 0x12, + + /** + Control. + */ + kControl = 0x20, +}; + +/** + A parsed \WebMID{TrackEntry} element. + */ +struct TrackEntry { + /** + A parsed \WebMID{TrackNumber} element. + */ + Element<std::uint64_t> track_number; + + /** + A parsed \WebMID{TrackUID} element. + */ + Element<std::uint64_t> track_uid; + + /** + A parsed \WebMID{TrackType} element. + */ + Element<TrackType> track_type; + + /** + A parsed \WebMID{FlagEnabled} element. + */ + Element<bool> is_enabled{true}; + + /** + A parsed \WebMID{FlagDefault} element. + */ + Element<bool> is_default{true}; + + /** + A parsed \WebMID{FlagForced} element. + */ + Element<bool> is_forced{false}; + + /** + A parsed \WebMID{FlagLacing} element. + */ + Element<bool> uses_lacing{true}; + + /** + A parsed \WebMID{DefaultDuration} element. + */ + Element<std::uint64_t> default_duration; + + /** + A parsed \WebMID{Name} element. + */ + Element<std::string> name; + + /** + A parsed \WebMID{Language} element. + */ + Element<std::string> language{"eng"}; + + /** + A parsed \WebMID{CodecID} element. + */ + Element<std::string> codec_id; + + /** + A parsed \WebMID{CodecPrivate} element. + */ + Element<std::vector<std::uint8_t>> codec_private; + + /** + A parsed \WebMID{CodecName} element. + */ + Element<std::string> codec_name; + + /** + A parsed \WebMID{CodecDelay} element. + */ + Element<std::uint64_t> codec_delay{0}; + + /** + A parsed \WebMID{SeekPreRoll} element. + */ + Element<std::uint64_t> seek_pre_roll{0}; + + /** + A parsed \WebMID{Video} element. + */ + Element<Video> video; + + /** + A parsed \WebMID{Audio} element. + */ + Element<Audio> audio; + + /** + A parsed \WebMID{ContentEncodings} element. + */ + Element<ContentEncodings> content_encodings; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const TrackEntry& other) const { + return track_number == other.track_number && track_uid == other.track_uid && + track_type == other.track_type && is_enabled == other.is_enabled && + is_default == other.is_default && is_forced == other.is_forced && + uses_lacing == other.uses_lacing && + default_duration == other.default_duration && name == other.name && + language == other.language && codec_id == other.codec_id && + codec_private == other.codec_private && + codec_name == other.codec_name && codec_delay == other.codec_delay && + seek_pre_roll == other.seek_pre_roll && video == other.video && + audio == other.audio && content_encodings == other.content_encodings; + } +}; + +/** + A parsed \WebMID{CueTrackPositions} element. + */ +struct CueTrackPositions { + /** + A parsed \WebMID{CueTrack} element. + */ + Element<std::uint64_t> track; + + /** + A parsed \WebMID{CueClusterPosition} element. + */ + Element<std::uint64_t> cluster_position; + + /** + A parsed \WebMID{CueRelativePosition} element. + */ + Element<std::uint64_t> relative_position; + + /** + A parsed \WebMID{CueDuration} element. + */ + Element<std::uint64_t> duration; + + /** + A parsed \WebMID{CueBlockNumber} element. + */ + Element<std::uint64_t> block_number{1}; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const CueTrackPositions& other) const { + return track == other.track && cluster_position == other.cluster_position && + relative_position == other.relative_position && + duration == other.duration && block_number == other.block_number; + } +}; + +/** + A parsed \WebMID{CuePoint} element. + */ +struct CuePoint { + /** + A parsed \WebMID{CueTime} element. + */ + Element<std::uint64_t> time; + + /** + Parsed \WebMID{CueTrackPositions} elements. + */ + std::vector<Element<CueTrackPositions>> cue_track_positions; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const CuePoint& other) const { + return time == other.time && + cue_track_positions == other.cue_track_positions; + } +}; + +/** + A parsed \WebMID{ChapterDisplay} element. + */ +struct ChapterDisplay { + /** + A parsed \WebMID{ChapString} element. + */ + Element<std::string> string; + + /** + Parsed \WebMID{ChapLanguage} elements. + */ + std::vector<Element<std::string>> languages{Element<std::string>{"eng"}}; + + /** + Parsed \WebMID{ChapCountry} elements. + */ + std::vector<Element<std::string>> countries; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const ChapterDisplay& other) const { + return string == other.string && languages == other.languages && + countries == other.countries; + } +}; + +/** + A parsed \WebMID{ChapterAtom} element. + */ +struct ChapterAtom { + /** + A parsed \WebMID{ChapterUID} element. + */ + Element<std::uint64_t> uid; + + /** + A parsed \WebMID{ChapterStringUID} element. + */ + Element<std::string> string_uid; + + /** + A parsed \WebMID{ChapterTimeStart} element. + */ + Element<std::uint64_t> time_start; + + /** + A parsed \WebMID{ChapterTimeEnd} element. + */ + Element<std::uint64_t> time_end; + + /** + Parsed \WebMID{ChapterDisplay} elements. + */ + std::vector<Element<ChapterDisplay>> displays; + + /** + Parsed \WebMID{ChapterAtom} elements. + */ + std::vector<Element<ChapterAtom>> atoms; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const ChapterAtom& other) const { + return uid == other.uid && string_uid == other.string_uid && + time_start == other.time_start && time_end == other.time_end && + displays == other.displays && atoms == other.atoms; + } +}; + +/** + A parsed \WebMID{EditionEntry} element. + */ +struct EditionEntry { + /** + Parsed \WebMID{ChapterAtom} elements. + */ + std::vector<Element<ChapterAtom>> atoms; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const EditionEntry& other) const { + return atoms == other.atoms; + } +}; + +/** + A parsed \WebMID{SimpleTag} element. + */ +struct SimpleTag { + /** + A parsed \WebMID{TagName} element. + */ + Element<std::string> name; + + /** + A parsed \WebMID{TagLanguage} element. + */ + Element<std::string> language{"und"}; + + /** + A parsed \WebMID{TagDefault} element. + */ + Element<bool> is_default{true}; + + /** + A parsed \WebMID{TagString} element. + */ + Element<std::string> string; + + /** + A parsed \WebMID{TagBinary} element. + */ + Element<std::vector<std::uint8_t>> binary; + + /** + Parsed \WebMID{SimpleTag} elements. + */ + std::vector<Element<SimpleTag>> tags; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const SimpleTag& other) const { + return name == other.name && language == other.language && + is_default == other.is_default && string == other.string && + binary == other.binary && tags == other.tags; + } +}; + +/** + A parsed \WebMID{Targets} element. + */ +struct Targets { + /** + A parsed \WebMID{TargetTypeValue} element. + */ + Element<std::uint64_t> type_value{50}; + + /** + A parsed \WebMID{TargetType} element. + */ + Element<std::string> type; + + /** + Parsed \WebMID{TagTrackUID} elements. + */ + std::vector<Element<std::uint64_t>> track_uids; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Targets& other) const { + return type_value == other.type_value && type == other.type && + track_uids == other.track_uids; + } +}; + +/** + A parsed \WebMID{Tag} element. + */ +struct Tag { + /** + A parsed \WebMID{Targets} element. + */ + Element<Targets> targets; + + /** + Parsed \WebMID{SimpleTag} elements. + */ + std::vector<Element<SimpleTag>> tags; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const Tag& other) const { + return targets == other.targets && tags == other.tags; + } +}; + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_DOM_TYPES_H_ diff --git a/webm_parser/include/webm/element.h b/webm_parser/include/webm/element.h new file mode 100644 index 0000000..a7f6235 --- /dev/null +++ b/webm_parser/include/webm/element.h @@ -0,0 +1,208 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_ELEMENT_H_ +#define INCLUDE_WEBM_ELEMENT_H_ + +#include <cstdint> +#include <limits> +#include <utility> + +#include "./id.h" + +/** + \file + A wrapper around an object that represents a WebM element, including its parsed + metadata. + */ + +namespace webm { + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + A wrapper around an object that represents a WebM element. + + Since some elements may be absent, this wrapper is used to indicate the + presence (or lack thereof) of an element in a WebM document. If the element is + encoded in the file and it has been parsed, `is_present()` will return true. + Otherwise it will return false since the element was ommitted or skipped when + parsing. + */ +template <typename T> +class Element { + public: + /** + Value-initializes the element's value and makes `is_present()` false. + */ + constexpr Element() = default; + + /** + Creates an element with the given value and makes `is_present()` false. + + \param value The value of the element. + */ + explicit constexpr Element(const T& value) : value_(value) {} + + /** + Creates an element with the given value and makes `is_present()` false. + + \param value The value of the element. + */ + explicit constexpr Element(T&& value) : value_(std::move(value)) {} + + /** + Creates an element with the given value and presence state. + + \param value The value of the element. + \param is_present True if the element is present, false if it is absent. + */ + constexpr Element(const T& value, bool is_present) + : value_(value), is_present_(is_present) {} + + /** + Creates an element with the given value and presence state. + + \param value The value of the element. + \param is_present True if the element is present, false if it is absent. + */ + constexpr Element(T&& value, bool is_present) + : value_(std::move(value)), is_present_(is_present) {} + + constexpr Element(const Element<T>& other) = default; + constexpr Element(Element<T>&& other) = default; + + ~Element() = default; + + Element<T>& operator=(const Element<T>& other) = default; + Element<T>& operator=(Element<T>&& other) = default; + + /** + Sets the element's value and state. + + \param value The new value for the element. + \param is_present The new presence state for the element. + */ + void Set(const T& value, bool is_present) { + value_ = value; + is_present_ = is_present; + } + + /** + Sets the element's value and state. + + \param value The new value for the element. + \param is_present The new presence state for the element. + */ + void Set(T&& value, bool is_present) { + value_ = std::move(value); + is_present_ = is_present; + } + + /** + Gets the element's value. + */ + constexpr const T& value() const { return value_; } + + /** + Gets a mutuable pointer to the element's value (will never be null). + */ + T* mutable_value() { return &value_; } + + /** + Returns true if the element is present, false otherwise. + */ + constexpr bool is_present() const { return is_present_; } + + bool operator==(const Element<T>& other) const { + return is_present_ == other.is_present_ && value_ == other.value_; + } + + private: + T value_{}; + bool is_present_ = false; +}; + +/** + Metadata for WebM elements that are encountered when parsing. + */ +struct ElementMetadata { + /** + The EBML ID of the element. + */ + Id id; + + /** + The number of bytes that were used to encode the EBML ID and element size. + + If the size of the header is unknown (which is only the case if a seek was + performed to the middle of an element, so its header was not parsed), this + will be the value `kUnknownHeaderSize`. + */ + std::uint32_t header_size; + + /** + The size of the element. + + This is number of bytes in the element's body, which excludes the header + bytes. + + If the size of the element's body is unknown, this will be the value + `kUnknownElementSize`. + */ + std::uint64_t size; + + /** + The absolute byte position of the element, starting at the first byte of the + element's header. + + If the position of the element is unknown (which is only the case if a seek + was performed to the middle of an element), this will be the value + `kUnknownElementPosition`. + */ + std::uint64_t position; + + /** + Returns true if every member within the two objects are equal. + */ + bool operator==(const ElementMetadata& other) const { + return id == other.id && header_size == other.header_size && + size == other.size && position == other.position; + } +}; + +/** + A special value for `ElementMetadata::header_size` indicating the header size + is not known. + */ +constexpr std::uint64_t kUnknownHeaderSize = + std::numeric_limits<std::uint32_t>::max(); + +/** + A special value for `ElementMetadata::size` indicating the element's size is + not known. + */ +constexpr std::uint64_t kUnknownElementSize = + std::numeric_limits<std::uint64_t>::max(); + +/** + A special value for `ElementMetadata::position` indicating the element's + position is not known. + */ +constexpr std::uint64_t kUnknownElementPosition = + std::numeric_limits<std::uint64_t>::max(); + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_ELEMENT_H_ diff --git a/webm_parser/include/webm/file_reader.h b/webm_parser/include/webm/file_reader.h new file mode 100644 index 0000000..6ccdc4d --- /dev/null +++ b/webm_parser/include/webm/file_reader.h @@ -0,0 +1,90 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_FILE_READER_H_ +#define INCLUDE_WEBM_FILE_READER_H_ + +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <memory> + +#include "./reader.h" +#include "./status.h" + +/** + \file + A `Reader` implementation that reads from a `FILE*`. + */ + +namespace webm { + +/** + A `Reader` implementation that can read from `FILE*` resources. + */ +class FileReader : public Reader { + public: + /** + Constructs a new, empty reader. + */ + FileReader() = default; + + /** + Constructs a new reader, using the provided file as the data source. + + Ownership of the file is taken, and `std::fclose()` will be called when the + object is destroyed. + + \param file The file to use as a data source. Must not be null. The file will + be closed (via `std::fclose()`) when the `FileReader`'s destructor runs. + */ + explicit FileReader(FILE* file); + + /** + Constructs a new reader by moving the provided reader into the new reader. + + \param other The source reader to move. After moving, it will be reset to an + empty stream. + */ + FileReader(FileReader&& other); + + /** + Moves the provided reader into this reader. + + \param other The source reader to move. After moving, it will be reset to an + empty stream. May be equal to `*this`, in which case this is a no-op. + \return `*this`. + */ + FileReader& operator=(FileReader&& other); + + Status Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) override; + + Status Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) override; + + std::uint64_t Position() const override; + + private: + struct FileCloseFunctor { + void operator()(FILE* file) const { + if (file) + std::fclose(file); + } + }; + + std::unique_ptr<FILE, FileCloseFunctor> file_; + + // We can't rely on ftell() for the position (since it only returns long, and + // doesn't work on things like pipes); we need to manually track the reading + // position. + std::uint64_t position_ = 0; +}; + +} // namespace webm + +#endif // INCLUDE_WEBM_FILE_READER_H_ diff --git a/webm_parser/include/webm/id.h b/webm_parser/include/webm/id.h new file mode 100644 index 0000000..0f761db --- /dev/null +++ b/webm_parser/include/webm/id.h @@ -0,0 +1,1085 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_ID_H_ +#define INCLUDE_WEBM_ID_H_ + +#include <cstdint> + +/** + \file + A full enumeration of WebM's EBML IDs. + */ + +namespace webm { + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + An EBML ID for a WebM element. + + The enum names correspond to the element names from the Matroska and WebM + specifications. See those specifications for further information on each + element. + */ +// For the WebM spec and element info, see: +// http://www.webmproject.org/docs/container/ +// http://www.webmproject.org/docs/webm-encryption/#42-new-matroskawebm-elements +// http://matroska.org/technical/specs/index.html +enum class Id : std::uint32_t { + // The MatroskaID alias links to the WebM and Matroska specifications. + // The WebMID alias links to the WebM specification. + // The WebMTable alias produces a table given the following arguments: + // Type, Level, Mandatory, Multiple, Recursive, Value range, Default value + + /** + \MatroskaID{EBML} element ID. + + \WebMTable{Master, 0, Yes, Yes, No, , } + */ + kEbml = 0x1A45DFA3, + + /** + \MatroskaID{EBMLVersion} element ID. + + \WebMTable{Unsigned integer, 1, Yes, No, No, , 1} + */ + kEbmlVersion = 0x4286, + + /** + \MatroskaID{EBMLReadVersion} element ID. + + \WebMTable{Unsigned integer, 1, Yes, No, No, , 1} + */ + kEbmlReadVersion = 0x42F7, + + /** + \MatroskaID{EBMLMaxIDLength} element ID. + + \WebMTable{Unsigned integer, 1, Yes, No, No, , 4} + */ + kEbmlMaxIdLength = 0x42F2, + + /** + \MatroskaID{EBMLMaxSizeLength} element ID. + + \WebMTable{Unsigned integer, 1, Yes, No, No, , 8} + */ + kEbmlMaxSizeLength = 0x42F3, + + /** + \MatroskaID{DocType} element ID. + + \WebMTable{ASCII string, 1, Yes, No, No, , matroska} + */ + kDocType = 0x4282, + + /** + \MatroskaID{DocTypeVersion} element ID. + + \WebMTable{Unsigned integer, 1, Yes, No, No, , 1} + */ + kDocTypeVersion = 0x4287, + + /** + \MatroskaID{DocTypeReadVersion} element ID. + + \WebMTable{Unsigned integer, 1, Yes, No, No, , 1} + */ + kDocTypeReadVersion = 0x4285, + + /** + \MatroskaID{Void} element ID. + + \WebMTable{Binary, g, No, No, No, , } + */ + kVoid = 0xEC, + + /** + \MatroskaID{Segment} element ID. + + \WebMTable{Master, 0, Yes, Yes, No, , } + */ + kSegment = 0x18538067, + + /** + \MatroskaID{SeekHead} element ID. + + \WebMTable{Master, 1, No, Yes, No, , } + */ + kSeekHead = 0x114D9B74, + + /** + \MatroskaID{Seek} element ID. + + \WebMTable{Master, 2, Yes, Yes, No, , } + */ + kSeek = 0x4DBB, + + /** + \MatroskaID{SeekID} element ID. + + \WebMTable{Binary, 3, Yes, No, No, , } + */ + kSeekId = 0x53AB, + + /** + \MatroskaID{SeekPosition} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, , 0} + */ + kSeekPosition = 0x53AC, + + /** + \MatroskaID{Info} element ID. + + \WebMTable{Master, 1, Yes, Yes, No, , } + */ + kInfo = 0x1549A966, + + /** + \MatroskaID{TimecodeScale} element ID. + + \WebMTable{Unsigned integer, 2, Yes, No, No, , 1000000} + */ + kTimecodeScale = 0x2AD7B1, + + /** + \MatroskaID{Duration} element ID. + + \WebMTable{Float, 2, No, No, No, > 0, 0} + */ + kDuration = 0x4489, + + /** + \MatroskaID{DateUTC} element ID. + + \WebMTable{Date, 2, No, No, No, , 0} + */ + kDateUtc = 0x4461, + + /** + \MatroskaID{Title} element ID. + + \WebMTable{UTF-8 string, 2, No, No, No, , } + */ + kTitle = 0x7BA9, + + /** + \MatroskaID{MuxingApp} element ID. + + \WebMTable{UTF-8 string, 2, Yes, No, No, , } + */ + kMuxingApp = 0x4D80, + + /** + \MatroskaID{WritingApp} element ID. + + \WebMTable{UTF-8 string, 2, Yes, No, No, , } + */ + kWritingApp = 0x5741, + + /** + \MatroskaID{Cluster} element ID. + + \WebMTable{Master, 1, No, Yes, No, , } + */ + kCluster = 0x1F43B675, + + /** + \MatroskaID{Timecode} element ID. + + \WebMTable{Unsigned integer, 2, Yes, No, No, , 0} + */ + kTimecode = 0xE7, + + /** + \MatroskaID{PrevSize} element ID. + + \WebMTable{Unsigned integer, 2, No, No, No, , 0} + */ + kPrevSize = 0xAB, + + /** + \MatroskaID{SimpleBlock} element ID. + + \WebMTable{Binary, 2, No, Yes, No, , } + */ + kSimpleBlock = 0xA3, + + /** + \MatroskaID{BlockGroup} element ID. + + \WebMTable{Master, 2, No, Yes, No, , } + */ + kBlockGroup = 0xA0, + + /** + \MatroskaID{Block} element ID. + + \WebMTable{Binary, 3, Yes, No, No, , } + */ + kBlock = 0xA1, + + /** + \MatroskaID{BlockVirtual} (deprecated) element ID. + + \WebMTable{Binary, 3, No, No, No, , } + */ + kBlockVirtual = 0xA2, + + /** + \MatroskaID{BlockAdditions} element ID. + + \WebMTable{Master, 3, No, No, No, , } + */ + kBlockAdditions = 0x75A1, + + /** + \MatroskaID{BlockMore} element ID. + + \WebMTable{Master, 4, Yes, Yes, No, , } + */ + kBlockMore = 0xA6, + + /** + \MatroskaID{BlockAddID} element ID. + + \WebMTable{Unsigned integer, 5, Yes, No, No, Not 0, 1} + */ + kBlockAddId = 0xEE, + + /** + \MatroskaID{BlockAdditional} element ID. + + \WebMTable{Binary, 5, Yes, No, No, , } + */ + kBlockAdditional = 0xA5, + + /** + \MatroskaID{BlockDuration} element ID. + + \WebMTable{Unsigned integer, 3, No, No, No, , DefaultDuration} + */ + kBlockDuration = 0x9B, + + /** + \MatroskaID{ReferenceBlock} element ID. + + \WebMTable{Signed integer, 3, No, Yes, No, , 0} + */ + kReferenceBlock = 0xFB, + + /** + \MatroskaID{DiscardPadding} element ID. + + \WebMTable{Signed integer, 3, No, No, No, , 0} + */ + kDiscardPadding = 0x75A2, + + /** + \MatroskaID{Slices} (deprecated). + + \WebMTable{Master, 3, No, No, No, , } + */ + kSlices = 0x8E, + + /** + \MatroskaID{TimeSlice} (deprecated) element ID. + + \WebMTable{Master, 4, No, Yes, No, , } + */ + kTimeSlice = 0xE8, + + /** + \MatroskaID{LaceNumber} (deprecated) element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kLaceNumber = 0xCC, + + /** + \MatroskaID{Tracks} element ID. + + \WebMTable{Master, 1, No, Yes, No, , } + */ + kTracks = 0x1654AE6B, + + /** + \MatroskaID{TrackEntry} element ID. + + \WebMTable{Master, 2, Yes, Yes, No, , } + */ + kTrackEntry = 0xAE, + + /** + \MatroskaID{TrackNumber} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, Not 0, 0} + */ + kTrackNumber = 0xD7, + + /** + \MatroskaID{TrackUID} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, Not 0, 0} + */ + kTrackUid = 0x73C5, + + /** + \MatroskaID{TrackType} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, 1-254, 0} + */ + kTrackType = 0x83, + + /** + \MatroskaID{FlagEnabled} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 1} + */ + kFlagEnabled = 0xB9, + + /** + \MatroskaID{FlagDefault} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 1} + */ + kFlagDefault = 0x88, + + /** + \MatroskaID{FlagForced} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 0} + */ + kFlagForced = 0x55AA, + + /** + \MatroskaID{FlagLacing} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 1} + */ + kFlagLacing = 0x9C, + + /** + \MatroskaID{DefaultDuration} element ID. + + \WebMTable{Unsigned integer, 3, No, No, No, Not 0, 0} + */ + kDefaultDuration = 0x23E383, + + /** + \MatroskaID{Name} element ID. + + \WebMTable{UTF-8 string, 3, No, No, No, , } + */ + kName = 0x536E, + + /** + \MatroskaID{Language} element ID. + + \WebMTable{ASCII string, 3, No, No, No, , eng} + */ + kLanguage = 0x22B59C, + + /** + \MatroskaID{CodecID} element ID. + + \WebMTable{ASCII string, 3, Yes, No, No, , } + */ + kCodecId = 0x86, + + /** + \MatroskaID{CodecPrivate} element ID. + + \WebMTable{Binary, 3, No, No, No, , } + */ + kCodecPrivate = 0x63A2, + + /** + \MatroskaID{CodecName} element ID. + + \WebMTable{UTF-8 string, 3, No, No, No, , } + */ + kCodecName = 0x258688, + + /** + \MatroskaID{CodecDelay} element ID. + + \WebMTable{Unsigned integer, 3, No, No, No, , 0} + */ + kCodecDelay = 0x56AA, + + /** + \MatroskaID{SeekPreRoll} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, , 0} + */ + kSeekPreRoll = 0x56BB, + + /** + \MatroskaID{Video} element ID. + + \WebMTable{Master, 3, No, No, No, , } + */ + kVideo = 0xE0, + + /** + \MatroskaID{FlagInterlaced} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, 0-1, 0} + */ + kFlagInterlaced = 0x9A, + + /** + \MatroskaID{StereoMode} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kStereoMode = 0x53B8, + + /** + \MatroskaID{AlphaMode} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kAlphaMode = 0x53C0, + + /** + \MatroskaID{PixelWidth} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0} + */ + kPixelWidth = 0xB0, + + /** + \MatroskaID{PixelHeight} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0} + */ + kPixelHeight = 0xBA, + + /** + \MatroskaID{PixelCropBottom} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kPixelCropBottom = 0x54AA, + + /** + \MatroskaID{PixelCropTop} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kPixelCropTop = 0x54BB, + + /** + \MatroskaID{PixelCropLeft} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kPixelCropLeft = 0x54CC, + + /** + \MatroskaID{PixelCropRight} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kPixelCropRight = 0x54DD, + + /** + \MatroskaID{DisplayWidth} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, Not 0, PixelWidth} + */ + kDisplayWidth = 0x54B0, + + /** + \MatroskaID{DisplayHeight} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, Not 0, PixelHeight} + */ + kDisplayHeight = 0x54BA, + + /** + \MatroskaID{DisplayUnit} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kDisplayUnit = 0x54B2, + + /** + \MatroskaID{AspectRatioType} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kAspectRatioType = 0x54B3, + + /** + \MatroskaID{FrameRate} (deprecated) element ID. + + \WebMTable{Float, 4, No, No, No, > 0, 0} + */ + kFrameRate = 0x2383E3, + + /** + \MatroskaID{Colour} element ID. + + \WebMTable{Master, 4, No, No, No, , } + */ + kColour = 0x55B0, + + /** + \MatroskaID{MatrixCoefficients} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 2} + */ + kMatrixCoefficients = 0x55B1, + + /** + \MatroskaID{BitsPerChannel} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kBitsPerChannel = 0x55B2, + + /** + \MatroskaID{ChromaSubsamplingHorz} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kChromaSubsamplingHorz = 0x55B3, + + /** + \MatroskaID{ChromaSubsamplingVert} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kChromaSubsamplingVert = 0x55B4, + + /** + \MatroskaID{CbSubsamplingHorz} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kCbSubsamplingHorz = 0x55B5, + + /** + \MatroskaID{CbSubsamplingVert} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kCbSubsamplingVert = 0x55B6, + + /** + \MatroskaID{ChromaSitingHorz} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kChromaSitingHorz = 0x55B7, + + /** + \MatroskaID{ChromaSitingVert} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kChromaSitingVert = 0x55B8, + + /** + \MatroskaID{Range} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kRange = 0x55B9, + + /** + \MatroskaID{TransferCharacteristics} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 2} + */ + kTransferCharacteristics = 0x55BA, + + /** + \MatroskaID{Primaries} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 2} + */ + kPrimaries = 0x55BB, + + /** + \MatroskaID{MaxCLL} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kMaxCll = 0x55BC, + + /** + \MatroskaID{MaxFALL} element ID. + + \WebMTable{Unsigned integer, 5, No, No, No, , 0} + */ + kMaxFall = 0x55BD, + + /** + \MatroskaID{MasteringMetadata} element ID. + + \WebMTable{Master, 5, No, No, No, , } + */ + kMasteringMetadata = 0x55D0, + + /** + \MatroskaID{PrimaryRChromaticityX} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kPrimaryRChromaticityX = 0x55D1, + + /** + \MatroskaID{PrimaryRChromaticityY} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kPrimaryRChromaticityY = 0x55D2, + + /** + \MatroskaID{PrimaryGChromaticityX} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kPrimaryGChromaticityX = 0x55D3, + + /** + \MatroskaID{PrimaryGChromaticityY} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kPrimaryGChromaticityY = 0x55D4, + + /** + \MatroskaID{PrimaryBChromaticityX} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kPrimaryBChromaticityX = 0x55D5, + + /** + \MatroskaID{PrimaryBChromaticityY} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kPrimaryBChromaticityY = 0x55D6, + + /** + \MatroskaID{WhitePointChromaticityX} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kWhitePointChromaticityX = 0x55D7, + + /** + \MatroskaID{WhitePointChromaticityY} element ID. + + \WebMTable{Float, 6, No, No, No, 0-1, 0} + */ + kWhitePointChromaticityY = 0x55D8, + + /** + \MatroskaID{LuminanceMax} element ID. + + \WebMTable{Float, 6, No, No, No, 0-9999.99, 0} + */ + kLuminanceMax = 0x55D9, + + /** + \MatroskaID{LuminanceMin} element ID. + + \WebMTable{Float, 6, No, No, No, 0-999.9999, 0} + */ + kLuminanceMin = 0x55DA, + + /** + \WebMID{Projection} element ID. + + \WebMTable{Master, 5, No, No, No, , } + */ + kProjection = 0x7670, + + /** + \WebMID{ProjectionType} element ID. + + \WebMTable{Unsigned integer, 6, Yes, No, No, , 0} + */ + kProjectionType = 0x7671, + + /** + \WebMID{ProjectionPrivate} element ID. + + \WebMTable{Binary, 6, No, No, No, , } + */ + kProjectionPrivate = 0x7672, + + /** + \WebMID{ProjectionPoseYaw} element ID. + + \WebMTable{Float, 6, Yes, No, No, , 0} + */ + kProjectionPoseYaw = 0x7673, + + /** + \WebMID{ProjectionPosePitch} element ID. + + \WebMTable{Float, 6, Yes, No, No, , 0} + */ + kProjectionPosePitch = 0x7674, + + /** + \WebMID{ProjectionPoseRoll} element ID. + + \WebMTable{Float, 6, Yes, No, No, , 0} + */ + kProjectionPoseRoll = 0x7675, + + /** + \MatroskaID{Audio} element ID. + + \WebMTable{Master, 3, No, No, No, , } + */ + kAudio = 0xE1, + + /** + \MatroskaID{SamplingFrequency} element ID. + + \WebMTable{Float, 4, Yes, No, No, > 0, 8000} + */ + kSamplingFrequency = 0xB5, + + /** + \MatroskaID{OutputSamplingFrequency} element ID. + + \WebMTable{Float, 4, No, No, No, > 0, SamplingFrequency} + */ + kOutputSamplingFrequency = 0x78B5, + + /** + \MatroskaID{Channels} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 1} + */ + kChannels = 0x9F, + + /** + \MatroskaID{BitDepth} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, Not 0, 0} + */ + kBitDepth = 0x6264, + + /** + \MatroskaID{ContentEncodings} element ID. + + \WebMTable{Master, 3, No, No, No, , } + */ + kContentEncodings = 0x6D80, + + /** + \MatroskaID{ContentEncoding} element ID. + + \WebMTable{Master, 4, Yes, Yes, No, , } + */ + kContentEncoding = 0x6240, + + /** + \MatroskaID{ContentEncodingOrder} element ID. + + \WebMTable{Unsigned integer, 5, Yes, No, No, , 0} + */ + kContentEncodingOrder = 0x5031, + + /** + \MatroskaID{ContentEncodingScope} element ID. + + \WebMTable{Unsigned integer, 5, Yes, No, No, Not 0, 1} + */ + kContentEncodingScope = 0x5032, + + /** + \MatroskaID{ContentEncodingType} element ID. + + \WebMTable{Unsigned integer, 5, Yes, No, No, , 0} + */ + kContentEncodingType = 0x5033, + + /** + \MatroskaID{ContentEncryption} element ID. + + \WebMTable{Master, 5, No, No, No, , } + */ + kContentEncryption = 0x5035, + + /** + \MatroskaID{ContentEncAlgo} element ID. + + \WebMTable{Unsigned integer, 6, No, No, No, , 0} + */ + kContentEncAlgo = 0x47E1, + + /** + \MatroskaID{ContentEncKeyID} element ID. + + \WebMTable{Binary, 6, No, No, No, , } + */ + kContentEncKeyId = 0x47E2, + + /** + \WebMID{ContentEncAESSettings} element ID. + + \WebMTable{Master, 6, No, No, No, , } + */ + kContentEncAesSettings = 0x47E7, + + /** + \WebMID{AESSettingsCipherMode} element ID. + + \WebMTable{Unsigned integer, 7, Yes, No, No, 1, 1} + */ + kAesSettingsCipherMode = 0x47E8, + + /** + \MatroskaID{Cues} element ID. + + \WebMTable{Master, 1, No, No, No, , } + */ + kCues = 0x1C53BB6B, + + /** + \MatroskaID{CuePoint} element ID. + + \WebMTable{Master, 2, Yes, Yes, No, , } + */ + kCuePoint = 0xBB, + + /** + \MatroskaID{CueTime} element ID. + + \WebMTable{Unsigned integer, 3, Yes, No, No, , 0} + */ + kCueTime = 0xB3, + + /** + \MatroskaID{CueTrackPositions} element ID. + + \WebMTable{Master, 3, Yes, Yes, No, , } + */ + kCueTrackPositions = 0xB7, + + /** + \MatroskaID{CueTrack} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0} + */ + kCueTrack = 0xF7, + + /** + \MatroskaID{CueClusterPosition} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, , 0} + */ + kCueClusterPosition = 0xF1, + + /** + \MatroskaID{CueRelativePosition} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kCueRelativePosition = 0xF0, + + /** + \MatroskaID{CueDuration} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kCueDuration = 0xB2, + + /** + \MatroskaID{CueBlockNumber} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, Not 0, 1} + */ + kCueBlockNumber = 0x5378, + + /** + \MatroskaID{Chapters} element ID. + + \WebMTable{Master, 1, No, No, No, , } + */ + kChapters = 0x1043A770, + + /** + \MatroskaID{EditionEntry} element ID. + + \WebMTable{Master, 2, Yes, Yes, No, , } + */ + kEditionEntry = 0x45B9, + + /** + \MatroskaID{ChapterAtom} element ID. + + \WebMTable{Master, 3, Yes, Yes, Yes, , } + */ + kChapterAtom = 0xB6, + + /** + \MatroskaID{ChapterUID} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0} + */ + kChapterUid = 0x73C4, + + /** + \MatroskaID{ChapterStringUID} element ID. + + \WebMTable{UTF-8 string, 4, No, No, No, , } + */ + kChapterStringUid = 0x5654, + + /** + \MatroskaID{ChapterTimeStart} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, , 0} + */ + kChapterTimeStart = 0x91, + + /** + \MatroskaID{ChapterTimeEnd} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 0} + */ + kChapterTimeEnd = 0x92, + + /** + \MatroskaID{ChapterDisplay} element ID. + + \WebMTable{Master, 4, No, Yes, No, , } + */ + kChapterDisplay = 0x80, + + /** + \MatroskaID{ChapString} element ID. + + \WebMTable{UTF-8 string, 5, Yes, No, No, , } + */ + kChapString = 0x85, + + /** + \MatroskaID{ChapLanguage} element ID. + + \WebMTable{ASCII string, 5, Yes, Yes, No, , eng} + */ + kChapLanguage = 0x437C, + + /** + \MatroskaID{ChapCountry} element ID. + + \WebMTable{ASCII string, 5, No, Yes, No, , } + */ + kChapCountry = 0x437E, + + /** + \MatroskaID{Tags} element ID. + + \WebMTable{Master, 1, No, Yes, No, , } + */ + kTags = 0x1254C367, + + /** + \MatroskaID{Tag} element ID. + + \WebMTable{Master, 2, Yes, Yes, No, , } + */ + kTag = 0x7373, + + /** + \MatroskaID{Targets} element ID. + + \WebMTable{Master, 3, Yes, No, No, , } + */ + kTargets = 0x63C0, + + /** + \MatroskaID{TargetTypeValue} element ID. + + \WebMTable{Unsigned integer, 4, No, No, No, , 50} + */ + kTargetTypeValue = 0x68CA, + + /** + \MatroskaID{TargetType} element ID. + + \WebMTable{ASCII string, 4, No, No, No, , } + */ + kTargetType = 0x63CA, + + /** + \MatroskaID{TagTrackUID} element ID. + + \WebMTable{Unsigned integer, 4, No, Yes, No, , 0} + */ + kTagTrackUid = 0x63C5, + + /** + \MatroskaID{SimpleTag} element ID. + + \WebMTable{Master, 3, Yes, Yes, Yes, , } + */ + kSimpleTag = 0x67C8, + + /** + \MatroskaID{TagName} element ID. + + \WebMTable{UTF-8 string, 4, Yes, No, No, , } + */ + kTagName = 0x45A3, + + /** + \MatroskaID{TagLanguage} element ID. + + \WebMTable{ASCII string, 4, Yes, No, No, , und} + */ + kTagLanguage = 0x447A, + + /** + \MatroskaID{TagDefault} element ID. + + \WebMTable{Unsigned integer, 4, Yes, No, No, 0-1, 1} + */ + kTagDefault = 0x4484, + + /** + \MatroskaID{TagString} element ID. + + \WebMTable{UTF-8 string, 4, No, No, No, , } + */ + kTagString = 0x4487, + + /** + \MatroskaID{TagBinary} element ID. + + \WebMTable{Binary, 4, No, No, No, , } + */ + kTagBinary = 0x4485, +}; + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_ID_H_ diff --git a/webm_parser/include/webm/istream_reader.h b/webm_parser/include/webm/istream_reader.h new file mode 100644 index 0000000..9de1053 --- /dev/null +++ b/webm_parser/include/webm/istream_reader.h @@ -0,0 +1,99 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_ISTREAM_READER_H_ +#define INCLUDE_WEBM_ISTREAM_READER_H_ + +#include <cstdint> +#include <cstdlib> +#include <istream> +#include <memory> +#include <type_traits> + +#include "./reader.h" +#include "./status.h" + +/** + \file + A `Reader` implementation that reads from a `std::istream`. + */ + +namespace webm { + +/** + A `Reader` implementation that can read from `std::istream`-based resources. + */ +class IstreamReader : public Reader { + public: + /** + Constructs a new, empty reader. + */ + IstreamReader() = default; + + /** + Constructs a new reader, using the provided `std::istream` as the data + source. + + Ownership of the stream is taken, and it will be moved into a new internal + instance. + + \param istream The stream to use as a data source. Must be an rvalue + reference derived from `std::istream`. + */ + template <typename T> + explicit IstreamReader(T&& istream) : istream_(new T(std::move(istream))) {} + + /** + Constructs a new reader by moving the provided reader into the new reader. + + \param other The source reader to move. After moving, it will be reset to an + empty stream. + */ + IstreamReader(IstreamReader&& other); + + /** + Moves the provided reader into this reader. + + \param other The source reader to move. After moving, it will be reset to an + empty stream. May be equal to `*this`, in which case this is a no-op. + \return `*this`. + */ + IstreamReader& operator=(IstreamReader&& other); + + Status Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) override; + + Status Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) override; + + std::uint64_t Position() const override; + + /** + Constructs a new reader and its data source in place. + + `T` must be derived from `std::istream` and constructible from the provided + arguments. + + \param args Arguments that will be forwarded to the `T`'s constructor. + \return A new `IstreamReader` backed by the underlying data source equivalent + to `T(std::forward<Args>(args)...`. + */ + template <typename T, typename... Args> + static IstreamReader Emplace(Args&&... args) { + IstreamReader reader; + reader.istream_.reset(new T(std::forward<Args>(args)...)); + return reader; + } + + private: + std::unique_ptr<std::istream> istream_; + std::uint64_t position_ = 0; +}; + +} // namespace webm + +#endif // INCLUDE_WEBM_ISTREAM_READER_H_ diff --git a/webm_parser/include/webm/reader.h b/webm_parser/include/webm/reader.h new file mode 100644 index 0000000..9275b50 --- /dev/null +++ b/webm_parser/include/webm/reader.h @@ -0,0 +1,94 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_READER_H_ +#define INCLUDE_WEBM_READER_H_ + +#include <cstddef> +#include <cstdint> + +#include "./status.h" + +/** + \file + An interface that acts as a data source for the parser to read from. + */ + +namespace webm { + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + A generic interface for reading from a data source. + + Throwing an exception from the member functions is permitted, though if the + exception will be caught and parsing resumed, then the reader should not + advance its position before throwing the exception. + */ +class Reader { + public: + virtual ~Reader() = default; + + /** + Reads data from the source and advances the reader's position by the number + of bytes read. + + Short reads are permitted, as is reading no data. + + \param num_to_read The number of bytes that should be read. Must not be 0. + \param buffer The buffer to store the read bytes. Must be large enough to + store at least `num_to_read` bytes. Must not be null. + \param[out] num_actually_read The number of bytes that were actually read is + stored in this integer. Must not be null. + \return `Status::kOkCompleted` if `num_to_read` bytes were read. + `Status::kOkPartial` if the number of bytes read is > 0 and < `num_to_read`. + If no bytes are read, then some status other than `Status::kOkCompleted` and + `Status::kOkPartial` must be returned and `num_actually_read` must be set to + 0. + */ + virtual Status Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) = 0; + + /** + Skips data from the source and advances the reader's position by the number + of bytes skipped. + + Short skips are permitted, as is skipping no data. This is similar to the + `Read()` method, but does not store data in an output buffer. + + \param num_to_skip The number of bytes that should be skipped. Must not be 0. + \param[out] num_actually_skipped The number of bytes that were actually + skipped is stored in this integer. Must not be null. + \return `Status::kOkCompleted` if `num_to_skip` bytes were skipped. + `Status::kOkPartial` if the number of bytes skipped is > 0 and < + `num_to_skip`. If no bytes are skipped, then some status other than + `Status::kOkCompleted` and `Status::kOkPartial` must be returned and + `num_actually_skipped` must be set to 0. + */ + virtual Status Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) = 0; + + /** + Gets the Reader's current absolute byte position in the stream. + + Implementations don't necessarily need to start from 0 (which might be the + case if parsing is starting in the middle of a data source). The value + `kUnknownElementPosition` must not be returned. + */ + virtual std::uint64_t Position() const = 0; +}; + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_READER_H_ diff --git a/webm_parser/include/webm/status.h b/webm_parser/include/webm/status.h new file mode 100644 index 0000000..ff3f0c6 --- /dev/null +++ b/webm_parser/include/webm/status.h @@ -0,0 +1,161 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_STATUS_H_ +#define INCLUDE_WEBM_STATUS_H_ + +#include <cstdint> + +/** + \file + Status information that represents success, failure, etc. for operations + throughout the API. + */ + +namespace webm { + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + An object used to represent the resulting status of various operations, + indicating success, failure, or some other result. + */ +struct Status { + /** + A list of generic status codes used by the parser. Users are encouraged to + reuse these values as needed in the derivations of `Callback`. These values + will always be <= 0. + */ + enum Code : std::int32_t { + // General status codes. Range: 0 to -1024. + /** + The operation successfully completed. + */ + kOkCompleted = 0, + + /** + The operation was successful but only partially completed (for example, a + read that resulted in fewer bytes than requested). + */ + kOkPartial = -1, + + /** + The operation would block, and should be tried again later. + */ + kWouldBlock = -2, + + /** + More data was requested but the file has ended. + */ + kEndOfFile = -3, + + // Parsing errors. Range: -1025 to -2048. + /** + An element's ID is malformed. + */ + kInvalidElementId = -1025, + + /** + An element's size is malformed. + */ + kInvalidElementSize = -1026, + + /** + An unknown element has unknown size. + */ + kIndefiniteUnknownElement = -1027, + + /** + A child element overflowed the parent element's bounds. + */ + kElementOverflow = -1028, + + /** + An element's size exceeds the system's memory limits. + */ + kNotEnoughMemory = -1029, + + /** + An element's value is illegal/malformed. + */ + kInvalidElementValue = -1030, + + /** + A recursive element was so deeply nested that exceeded the parser's limit. + */ + kExceededRecursionDepthLimit = -1031, + + // The following codes are internal-only and should not be used by users. + // Additionally, these codes should never be returned to the user; doing so + // is considered a bug. + /** + \internal Parsing should switch from reading to skipping elements. + */ + kSwitchToSkip = INT32_MIN, + }; + + /** + Status codes <= 0 are reserved by the parsing library. User error codes + should be positive (> 0). Users are encouraged to use codes from the `Code` + enum, but may use a positive error code if some application-specific error is + encountered. + */ + std::int32_t code; + + Status() = default; + Status(const Status&) = default; + Status(Status&&) = default; + Status& operator=(const Status&) = default; + Status& operator=(Status&&) = default; + + /** + Creates a new `Status` object with the given status code. + + \param code The status code which will be used to set the `code` member. + */ + constexpr explicit Status(Code code) : code(code) {} + + /** + Creates a new `Status` object with the given status code. + + \param code The status code which will be used to set the `code` member. + */ + constexpr explicit Status(std::int32_t code) : code(code) {} + + /** + Returns true if the status code is either `kOkCompleted` or `kOkPartial`. + Provided for convenience. + */ + constexpr bool ok() const { + return code == kOkCompleted || code == kOkPartial; + } + + /** + Returns true if the status code is `kOkCompleted`. Provided for convenience. + */ + constexpr bool completed_ok() const { return code == kOkCompleted; } + + /** + Returns true if the status is considered a parsing error. Parsing errors + represent unrecoverable errors due to malformed data. Only status codes in + the range -2048 to -1025 (inclusive) are considered parsing errors. + */ + constexpr bool is_parsing_error() const { + return -2048 <= code && code <= -1025; + } +}; + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_STATUS_H_ diff --git a/webm_parser/include/webm/webm_parser.h b/webm_parser/include/webm/webm_parser.h new file mode 100644 index 0000000..6b794d5 --- /dev/null +++ b/webm_parser/include/webm/webm_parser.h @@ -0,0 +1,133 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef INCLUDE_WEBM_WEBM_PARSER_H_ +#define INCLUDE_WEBM_WEBM_PARSER_H_ + +#include <memory> + +#include "./callback.h" +#include "./reader.h" +#include "./status.h" + +/** + \file + The main parser class for parsing WebM files. + */ + +namespace webm { + +/** + \defgroup PUBLIC_API Public API + Public types and header files intended for use by users. + */ + +/** + \addtogroup PUBLIC_API + @{ + */ + +/** + Incrementally parses a WebM document encoded in a byte stream. + + It is expected that the parsing will begin at the start of the WebM + file/document. Otherwise, the `DidSeek()` method should be used to inform the + parser that reading may not necessarily begin at the beginning of the document + and the parser should be prepared to handle data at an arbitrary point in the + document. + + WebM files are mostly a subset of Matroska, with a few small modifications. + Matroska is a format built on top of EBML. + */ +// Spec references: +// http://www.webmproject.org/docs/container/ +// https://matroska.org/technical/specs/index.html +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown +class WebmParser { + public: + /** + Constructs the object and prepares it for parsing. + */ + WebmParser(); + + /* + Cleans up the parser. No further cleanup is required from the user. + */ + ~WebmParser(); + + // Non-copyable and non-movable. + WebmParser(const WebmParser&) = delete; + WebmParser& operator=(const WebmParser&) = delete; + + /** + Resets the parser after a seek was performed on the reader, which prepares + the parser for starting at an arbitrary point in the stream. + + The seek must be to the start of an element (that is, it can't be any random + byte) or parsing will fail. + */ + void DidSeek(); + + /** + Feeds data into the parser from the provided reader. + + If a parsing error code has been returned (which indicates a malformed + document), calling Feed() again will just result in the same error; parsing + has already failed at that point and further progress can't be made. + + \param callback The callback which receives parsing events. No internal + references are maintained to `callback`, so it may be modified or freed after + this method returns. + \param reader The reader the parser will use to request data. No internal + references are maintained to `reader`, so it may be modified or freed after + this method returns. + \return `Status::kOkCompleted` when parsing completes successfully. + `Status::kOkPartial` or another non-parsing error code (see status.h for + error codes classified as parsing errors) will be returned if parsing has + only partially completed, and Feed() should be called again to resume + parsing. + */ + Status Feed(Callback* callback, Reader* reader); + + /** + Swaps this parser and the provided parser. + + \param other The parser to swap values/states with. Must not be null. + */ + void Swap(WebmParser* other); + + private: + // This header can only rely on sources in the public API. + class DocumentParser; + + // The internal implementation of the parser. + std::unique_ptr<DocumentParser> parser_; + + // The status of the parser, used to prevent further progress if an + // unrecoverable parsing error has already been encountered. + Status parsing_status_ = Status(Status::kOkPartial); +}; + +/** + Swaps the two parsers. + + This is provided so code can use argument dependent lookup in an idiomatic way + when swapping (especially since `std::swap` won't work since the parser is + non-movable). + + \param left The first parser to swap. + \param right The second parser to swap. + */ +void swap(WebmParser& left, WebmParser& right); + +/** + @} + */ + +} // namespace webm + +#endif // INCLUDE_WEBM_WEBM_PARSER_H_ diff --git a/webm_parser/src/ancestory.cc b/webm_parser/src/ancestory.cc new file mode 100644 index 0000000..9860a28 --- /dev/null +++ b/webm_parser/src/ancestory.cc @@ -0,0 +1,334 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/ancestory.h" + +#include "webm/id.h" + +namespace webm { + +bool Ancestory::ById(Id id, Ancestory* ancestory) { + // These lists of IDs were generated and must match the switch statement and + // have static storage duration. They were generated as follows: + // + // 1. List all the master elements: + // kEbml + // kSegment + // kSeekHead + // kSeek + // kInfo + // kCluster + // kBlockGroup + // kBlockAdditions + // kBlockMore + // kSlices + // kTimeSlice + // etc. + // + // 2. Now prefix each entry with its full ancestory: + // kEbml + // kSegment + // kSegment, kSeekHead + // kSegment, kSeekHead, kSeek + // kSegment, kInfo + // kSegment, kCluster + // kSegment, kCluster, kBlockGroup + // kSegment, kCluster, kBlockGroup, kBlockAdditions + // kSegment, kCluster, kBlockGroup, kBlockAdditions, kBlockMore + // kSegment, kCluster, kBlockGroup, kSlices + // kSegment, kCluster, kBlockGroup, kSlices, kTimeSlice + // etc. + // + // 3. Now remove entries that are just subsets of others: + // kEbml + // kSegment, kSeekHead, kSeek + // kSegment, kInfo + // kSegment, kCluster, kBlockGroup, kBlockAdditions, kBlockMore + // kSegment, kCluster, kBlockGroup, kSlices, kTimeSlice + // etc. + static constexpr Id kEbmlAncestory[] = { + Id::kEbml, + }; + static constexpr Id kSeekAncestory[] = { + Id::kSegment, + Id::kSeekHead, + Id::kSeek, + }; + static constexpr Id kInfoAncestory[] = { + Id::kSegment, + Id::kInfo, + }; + static constexpr Id kBlockMoreAncestory[] = { + Id::kSegment, Id::kCluster, Id::kBlockGroup, + Id::kBlockAdditions, Id::kBlockMore, + }; + static constexpr Id kTimeSliceAncestory[] = { + Id::kSegment, Id::kCluster, Id::kBlockGroup, Id::kSlices, Id::kTimeSlice, + }; + static constexpr Id kVideoAncestory[] = { + Id::kSegment, + Id::kTracks, + Id::kTrackEntry, + Id::kVideo, + }; + static constexpr Id kAudioAncestory[] = { + Id::kSegment, + Id::kTracks, + Id::kTrackEntry, + Id::kAudio, + }; + static constexpr Id kContentEncAesSettingsAncestory[] = { + Id::kSegment, + Id::kTracks, + Id::kTrackEntry, + Id::kContentEncodings, + Id::kContentEncoding, + Id::kContentEncryption, + Id::kContentEncAesSettings, + }; + static constexpr Id kCueTrackPositionsAncestory[] = { + Id::kSegment, + Id::kCues, + Id::kCuePoint, + Id::kCueTrackPositions, + }; + static constexpr Id kChapterDisplayAncestory[] = { + Id::kSegment, Id::kChapters, Id::kEditionEntry, + Id::kChapterAtom, Id::kChapterDisplay, + }; + static constexpr Id kTargetsAncestory[] = { + Id::kSegment, + Id::kTags, + Id::kTag, + Id::kTargets, + }; + static constexpr Id kSimpleTagAncestory[] = { + Id::kSegment, + Id::kTags, + Id::kTag, + Id::kSimpleTag, + }; + + switch (id) { + case Id::kEbmlVersion: + case Id::kEbmlReadVersion: + case Id::kEbmlMaxIdLength: + case Id::kEbmlMaxSizeLength: + case Id::kDocType: + case Id::kDocTypeVersion: + case Id::kDocTypeReadVersion: + *ancestory = Ancestory(kEbmlAncestory, 1); + return true; + + case Id::kSeekHead: + case Id::kInfo: + case Id::kCluster: + case Id::kTracks: + case Id::kCues: + case Id::kChapters: + case Id::kTags: + *ancestory = Ancestory(kSeekAncestory, 1); + return true; + + case Id::kSeek: + *ancestory = Ancestory(kSeekAncestory, 2); + return true; + + case Id::kSeekId: + case Id::kSeekPosition: + *ancestory = Ancestory(kSeekAncestory, 3); + return true; + + case Id::kTimecodeScale: + case Id::kDuration: + case Id::kDateUtc: + case Id::kTitle: + case Id::kMuxingApp: + case Id::kWritingApp: + *ancestory = Ancestory(kInfoAncestory, 2); + return true; + + case Id::kTimecode: + case Id::kPrevSize: + case Id::kSimpleBlock: + case Id::kBlockGroup: + *ancestory = Ancestory(kBlockMoreAncestory, 2); + return true; + + case Id::kBlock: + case Id::kBlockVirtual: + case Id::kBlockAdditions: + case Id::kBlockDuration: + case Id::kReferenceBlock: + case Id::kDiscardPadding: + case Id::kSlices: + *ancestory = Ancestory(kBlockMoreAncestory, 3); + return true; + + case Id::kBlockMore: + *ancestory = Ancestory(kBlockMoreAncestory, 4); + return true; + + case Id::kBlockAddId: + case Id::kBlockAdditional: + *ancestory = Ancestory(kBlockMoreAncestory, 5); + return true; + + case Id::kTimeSlice: + *ancestory = Ancestory(kTimeSliceAncestory, 4); + return true; + + case Id::kLaceNumber: + *ancestory = Ancestory(kTimeSliceAncestory, 5); + return true; + + case Id::kTrackEntry: + *ancestory = Ancestory(kVideoAncestory, 2); + return true; + + case Id::kTrackNumber: + case Id::kTrackUid: + case Id::kTrackType: + case Id::kFlagEnabled: + case Id::kFlagDefault: + case Id::kFlagForced: + case Id::kFlagLacing: + case Id::kDefaultDuration: + case Id::kName: + case Id::kLanguage: + case Id::kCodecId: + case Id::kCodecPrivate: + case Id::kCodecName: + case Id::kCodecDelay: + case Id::kSeekPreRoll: + case Id::kVideo: + case Id::kAudio: + case Id::kContentEncodings: + *ancestory = Ancestory(kVideoAncestory, 3); + return true; + + case Id::kFlagInterlaced: + case Id::kStereoMode: + case Id::kAlphaMode: + case Id::kPixelWidth: + case Id::kPixelHeight: + case Id::kPixelCropBottom: + case Id::kPixelCropTop: + case Id::kPixelCropLeft: + case Id::kPixelCropRight: + case Id::kDisplayWidth: + case Id::kDisplayHeight: + case Id::kDisplayUnit: + case Id::kAspectRatioType: + case Id::kFrameRate: + *ancestory = Ancestory(kVideoAncestory, 4); + return true; + + case Id::kSamplingFrequency: + case Id::kOutputSamplingFrequency: + case Id::kChannels: + case Id::kBitDepth: + *ancestory = Ancestory(kAudioAncestory, 4); + return true; + + case Id::kContentEncoding: + *ancestory = Ancestory(kContentEncAesSettingsAncestory, 4); + return true; + + case Id::kContentEncodingOrder: + case Id::kContentEncodingScope: + case Id::kContentEncodingType: + case Id::kContentEncryption: + *ancestory = Ancestory(kContentEncAesSettingsAncestory, 5); + return true; + + case Id::kContentEncAlgo: + case Id::kContentEncKeyId: + case Id::kContentEncAesSettings: + *ancestory = Ancestory(kContentEncAesSettingsAncestory, 6); + return true; + + case Id::kAesSettingsCipherMode: + *ancestory = Ancestory(kContentEncAesSettingsAncestory, 7); + return true; + + case Id::kCuePoint: + *ancestory = Ancestory(kCueTrackPositionsAncestory, 2); + return true; + + case Id::kCueTime: + case Id::kCueTrackPositions: + *ancestory = Ancestory(kCueTrackPositionsAncestory, 3); + return true; + + case Id::kCueTrack: + case Id::kCueClusterPosition: + case Id::kCueRelativePosition: + case Id::kCueDuration: + case Id::kCueBlockNumber: + *ancestory = Ancestory(kCueTrackPositionsAncestory, 4); + return true; + + case Id::kEditionEntry: + *ancestory = Ancestory(kChapterDisplayAncestory, 2); + return true; + + case Id::kChapterAtom: + *ancestory = Ancestory(kChapterDisplayAncestory, 3); + return true; + + case Id::kChapterUid: + case Id::kChapterStringUid: + case Id::kChapterTimeStart: + case Id::kChapterTimeEnd: + case Id::kChapterDisplay: + *ancestory = Ancestory(kChapterDisplayAncestory, 4); + return true; + + case Id::kChapString: + case Id::kChapLanguage: + case Id::kChapCountry: + *ancestory = Ancestory(kChapterDisplayAncestory, 5); + return true; + + case Id::kTag: + *ancestory = Ancestory(kTargetsAncestory, 2); + return true; + + case Id::kTargets: + case Id::kSimpleTag: + *ancestory = Ancestory(kTargetsAncestory, 3); + return true; + + case Id::kTargetTypeValue: + case Id::kTargetType: + case Id::kTagTrackUid: + *ancestory = Ancestory(kTargetsAncestory, 4); + return true; + + case Id::kTagName: + case Id::kTagLanguage: + case Id::kTagDefault: + case Id::kTagString: + case Id::kTagBinary: + *ancestory = Ancestory(kSimpleTagAncestory, 4); + return true; + + case Id::kEbml: + case Id::kSegment: + *ancestory = {}; + return true; + + default: + // This is an unknown element or a global element (i.e. Void); its + // ancestory cannot be deduced. + *ancestory = {}; + return false; + } +} + +} // namespace webm diff --git a/webm_parser/src/ancestory.h b/webm_parser/src/ancestory.h new file mode 100644 index 0000000..793b7e3 --- /dev/null +++ b/webm_parser/src/ancestory.h @@ -0,0 +1,83 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_ANCESTORY_H_ +#define SRC_ANCESTORY_H_ + +#include <cassert> +#include <cstddef> +#include <iterator> + +#include "webm/id.h" + +namespace webm { + +// Represents an element's ancestory in descending order. For example, the +// Id::kTrackNumber element has an ancestory of {Id::kSegment, Id::kTracks, +// Id::kTrackEntry}. +class Ancestory { + public: + // Constructs an empty ancestory. + Ancestory() = default; + + Ancestory(const Ancestory&) = default; + Ancestory(Ancestory&&) = default; + Ancestory& operator=(const Ancestory&) = default; + Ancestory& operator=(Ancestory&&) = default; + + // Returns the ancestory with the top-level parent removed. For example, if + // the current ancestory is {Id::kSegment, Id::kTracks, Id::kTrackEntry}, next + // will return {Id::kTracks, Id::kTrackEntry}. This must not be called if the + // ancestory is empty. + Ancestory next() const { + assert(begin_ < end_); + Ancestory copy = *this; + ++copy.begin_; + return copy; + } + + // Gets the Id of the top-level parent. For example, if the current ancestory + // is {Id::kSegment, Id::kTracks, Id::kTrackEntry}, id will return + // Id::kSegment. This must not be called if the ancestory is empty. + Id id() const { + assert(begin_ < end_); + return *begin_; + } + + // Returns true if the ancestory is empty. + bool empty() const { return begin_ == end_; } + + // Looks up the ancestory of the given id. Returns true and sets ancestory if + // the element's ancestory could be deduced. Global elements (i.e. Id::kVoid) + // and unknown elements can't have their ancestory deduced. + static bool ById(Id id, Ancestory* ancestory); + + private: + // Constructs an Ancestory using the first count elements of ancestory. + // ancestory must have static storage duration. + template <std::size_t N> + Ancestory(const Id (&ancestory)[N], std::size_t count) + : begin_(ancestory), end_(ancestory + count) { + assert(count <= N); + } + + // The following invariants apply to begin_ and end_: + // begin_ <= end_ + // (begin_ == end_) || (std::begin(kIds) <= begin_ && end_ <= std::end(kIds)) + + // The beginning (inclusive) of the sequence of IDs in kIds that defines the + // ancestory. + const Id* begin_ = nullptr; + + // The ending (exclusive) of the sequence of IDs in kIds that defines the + // ancestory. + const Id* end_ = nullptr; +}; + +} // namespace webm + +#endif // SRC_ANCESTORY_H_ diff --git a/webm_parser/src/audio_parser.h b/webm_parser/src/audio_parser.h new file mode 100644 index 0000000..eabab1f --- /dev/null +++ b/webm_parser/src/audio_parser.h @@ -0,0 +1,84 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_AUDIO_PARSER_H_ +#define SRC_AUDIO_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/float_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/callback.h" +#include "webm/dom_types.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Audio +// http://www.webmproject.org/docs/container/#Audio +class AudioParser : public MasterValueParser<Audio> { + public: + AudioParser() + : MasterValueParser<Audio>( + MakeChild<FloatParser>(Id::kSamplingFrequency, + &Audio::sampling_frequency), + MakeChild<FloatParser>(Id::kOutputSamplingFrequency, + &Audio::output_frequency) + .NotifyOnParseComplete(), + MakeChild<UnsignedIntParser>(Id::kChannels, &Audio::channels), + MakeChild<UnsignedIntParser>(Id::kBitDepth, &Audio::bit_depth)) {} + + Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) override { + output_frequency_has_value_ = false; + + return MasterValueParser::Init(metadata, max_size); + } + + void InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) override { + output_frequency_has_value_ = false; + + return MasterValueParser::InitAfterSeek(child_ancestory, child_metadata); + } + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + const Status status = + MasterValueParser::Feed(callback, reader, num_bytes_read); + if (status.completed_ok()) { + FixMissingOutputFrequency(); + } + return status; + } + + protected: + void OnChildParsed(const ElementMetadata& metadata) override { + assert(metadata.id == Id::kOutputSamplingFrequency); + + output_frequency_has_value_ = metadata.size > 0; + } + + private: + bool output_frequency_has_value_; + + void FixMissingOutputFrequency() { + if (!output_frequency_has_value_) { + *mutable_value()->output_frequency.mutable_value() = + value().sampling_frequency.value(); + } + } +}; + +} // namespace webm + +#endif // SRC_AUDIO_PARSER_H_ diff --git a/webm_parser/src/bit_utils.cc b/webm_parser/src/bit_utils.cc new file mode 100644 index 0000000..3956be0 --- /dev/null +++ b/webm_parser/src/bit_utils.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/bit_utils.h" + +#include <cstdint> + +namespace webm { + +std::uint8_t CountLeadingZeros(std::uint8_t value) { + // Special case for 0 since we can't shift by sizeof(T) * 8 bytes. + if (value == 0) + return 8; + + std::uint8_t count = 0; + while (!(value & (0x80 >> count))) { + ++count; + } + return count; +} + +} // namespace webm diff --git a/webm_parser/src/bit_utils.h b/webm_parser/src/bit_utils.h new file mode 100644 index 0000000..b5e90b6 --- /dev/null +++ b/webm_parser/src/bit_utils.h @@ -0,0 +1,24 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BIT_UTILS_H_ +#define SRC_BIT_UTILS_H_ + +#include <cstdint> + +namespace webm { + +// Counts the number of leading zero bits. +// For example: +// assert(8 == CountLeadingZeros(0x00)); +// assert(4 == CountLeadingZeros(0x0f)); +// assert(0 == CountLeadingZeros(0xf0)); +std::uint8_t CountLeadingZeros(std::uint8_t value); + +} // namespace webm + +#endif // SRC_BIT_UTILS_H_ diff --git a/webm_parser/src/block_additions_parser.h b/webm_parser/src/block_additions_parser.h new file mode 100644 index 0000000..be15e26 --- /dev/null +++ b/webm_parser/src/block_additions_parser.h @@ -0,0 +1,30 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BLOCK_ADDITIONS_PARSER_H_ +#define SRC_BLOCK_ADDITIONS_PARSER_H_ + +#include "src/block_more_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#BlockAdditions +// http://www.webmproject.org/docs/container/#BlockAdditions +class BlockAdditionsParser : public MasterValueParser<BlockAdditions> { + public: + BlockAdditionsParser() + : MasterValueParser<BlockAdditions>(MakeChild<BlockMoreParser>( + Id::kBlockMore, &BlockAdditions::block_mores)) {} +}; + +} // namespace webm + +#endif // SRC_BLOCK_ADDITIONS_PARSER_H_ diff --git a/webm_parser/src/block_group_parser.h b/webm_parser/src/block_group_parser.h new file mode 100644 index 0000000..feca6a4 --- /dev/null +++ b/webm_parser/src/block_group_parser.h @@ -0,0 +1,71 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BLOCK_GROUP_PARSER_H_ +#define SRC_BLOCK_GROUP_PARSER_H_ + +#include "src/block_additions_parser.h" +#include "src/block_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "src/slices_parser.h" +#include "src/virtual_block_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#BlockGroup +// http://www.webmproject.org/docs/container/#BlockGroup +class BlockGroupParser : public MasterValueParser<BlockGroup> { + public: + BlockGroupParser() + : MasterValueParser<BlockGroup>( + MakeChild<BlockParser>(Id::kBlock, &BlockGroup::block), + MakeChild<VirtualBlockParser>(Id::kBlockVirtual, + &BlockGroup::virtual_block), + MakeChild<BlockAdditionsParser>(Id::kBlockAdditions, + &BlockGroup::additions), + MakeChild<UnsignedIntParser>(Id::kBlockDuration, + &BlockGroup::duration), + MakeChild<SignedIntParser>(Id::kReferenceBlock, + &BlockGroup::references), + MakeChild<SignedIntParser>(Id::kDiscardPadding, + &BlockGroup::discard_padding), + MakeChild<SlicesParser>(Id::kSlices, &BlockGroup::slices)) {} + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + *num_bytes_read = 0; + + if (!parse_started_event_completed()) { + Action action = Action::kRead; + Status status = OnParseStarted(callback, &action); + if (!status.completed_ok()) { + return status; + } + + set_parse_started_event_completed_with_action(action); + } + + return MasterValueParser::Feed(callback, reader, num_bytes_read); + } + + protected: + Status OnParseStarted(Callback* callback, Action* action) override { + return callback->OnBlockGroupBegin(metadata(Id::kBlockGroup), action); + } + + Status OnParseCompleted(Callback* callback) override { + return callback->OnBlockGroupEnd(metadata(Id::kBlockGroup), value()); + } +}; + +} // namespace webm + +#endif // SRC_BLOCK_GROUP_PARSER_H_ diff --git a/webm_parser/src/block_header_parser.cc b/webm_parser/src/block_header_parser.cc new file mode 100644 index 0000000..59921ae --- /dev/null +++ b/webm_parser/src/block_header_parser.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/block_header_parser.h" + +#include <cassert> +#include <cstdint> + +#include "src/parser_utils.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#block_structure +// http://matroska.org/technical/specs/index.html#simpleblock_structure +// http://matroska.org/technical/specs/index.html#block_virtual +Status BlockHeaderParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + Status status; + std::uint64_t local_num_bytes_read; + + while (true) { + switch (state_) { + case State::kReadingTrackNumber: { + status = uint_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + if (!status.completed_ok()) { + return status; + } + value_.track_number = uint_parser_.value(); + state_ = State::kReadingTimecode; + continue; + } + + case State::kReadingTimecode: { + status = + AccumulateIntegerBytes(timecode_bytes_remaining_, reader, + &value_.timecode, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + timecode_bytes_remaining_ -= static_cast<int>(local_num_bytes_read); + if (!status.completed_ok()) { + return status; + } + state_ = State::kReadingFlags; + continue; + } + + case State::kReadingFlags: { + assert(timecode_bytes_remaining_ == 0); + status = ReadByte(reader, &value_.flags); + if (!status.completed_ok()) { + return status; + } + ++*num_bytes_read; + state_ = State::kDone; + continue; + } + + case State::kDone: { + return Status(Status::kOkCompleted); + } + } + } +} + +} // namespace webm diff --git a/webm_parser/src/block_header_parser.h b/webm_parser/src/block_header_parser.h new file mode 100644 index 0000000..9ce35e1 --- /dev/null +++ b/webm_parser/src/block_header_parser.h @@ -0,0 +1,65 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BLOCK_HEADER_PARSER_H_ +#define SRC_BLOCK_HEADER_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/parser.h" +#include "src/var_int_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +struct BlockHeader { + std::uint64_t track_number; + std::int16_t timecode; + std::uint8_t flags; + + bool operator==(const BlockHeader& other) const { + return track_number == other.track_number && timecode == other.timecode && + flags == other.flags; + } +}; + +class BlockHeaderParser : public Parser { + public: + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed block header information. This must not be called until the + // parse has been successfully completed. + const BlockHeader& value() const { + assert(state_ == State::kDone); + return value_; + } + + private: + BlockHeader value_; + + VarIntParser uint_parser_; + + int timecode_bytes_remaining_ = 2; + + enum class State { + /* clang-format off */ + // State Transitions to state When + kReadingTrackNumber, // kReadingTimecode track parsed + kReadingTimecode, // kReadingFlags timecode parsed + kReadingFlags, // kDone flags parsed + kDone, // No transitions from here (must call Init) + /* clang-format on */ + } state_ = State::kReadingTrackNumber; +}; + +} // namespace webm + +#endif // SRC_BLOCK_HEADER_PARSER_H_ diff --git a/webm_parser/src/block_more_parser.h b/webm_parser/src/block_more_parser.h new file mode 100644 index 0000000..1ab850d --- /dev/null +++ b/webm_parser/src/block_more_parser.h @@ -0,0 +1,32 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BLOCK_MORE_PARSER_H_ +#define SRC_BLOCK_MORE_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#BlockMore +// http://www.webmproject.org/docs/container/#BlockMore +class BlockMoreParser : public MasterValueParser<BlockMore> { + public: + BlockMoreParser() + : MasterValueParser<BlockMore>( + MakeChild<UnsignedIntParser>(Id::kBlockAddId, &BlockMore::id), + MakeChild<BinaryParser>(Id::kBlockAdditional, &BlockMore::data)) {} +}; + +} // namespace webm + +#endif // SRC_BLOCK_MORE_PARSER_H_ diff --git a/webm_parser/src/block_parser.cc b/webm_parser/src/block_parser.cc new file mode 100644 index 0000000..b851f51 --- /dev/null +++ b/webm_parser/src/block_parser.cc @@ -0,0 +1,285 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/block_parser.h" + +#include <cassert> +#include <cstdint> +#include <numeric> +#include <type_traits> +#include <vector> + +#include "src/parser_utils.h" +#include "webm/element.h" + +namespace webm { + +namespace { + +// The ParseBasicBlockFlags functions parse extra flag bits into the block, +// depending on the type of block that is being parsed. +void ParseBasicBlockFlags(std::uint8_t /* flags */, Block* /* block */) { + // Block has no extra flags that aren't already handled. +} + +void ParseBasicBlockFlags(std::uint8_t flags, SimpleBlock* block) { + block->is_key_frame = (0x80 & flags) != 0; + block->is_discardable = (0x01 & flags) != 0; +} + +// The BasicBlockBegin functions call the Callback event handler and get the +// correct action for the parser, depending on the type of block that is being +// parsed. +Status BasicBlockBegin(const ElementMetadata& metadata, const Block& block, + Callback* callback, Action* action) { + return callback->OnBlockBegin(metadata, block, action); +} + +Status BasicBlockBegin(const ElementMetadata& metadata, + const SimpleBlock& block, Callback* callback, + Action* action) { + return callback->OnSimpleBlockBegin(metadata, block, action); +} + +// The BasicBlockEnd functions call the Callback event handler depending on the +// type of block that is being parsed. +Status BasicBlockEnd(const ElementMetadata& metadata, const Block& block, + Callback* callback) { + return callback->OnBlockEnd(metadata, block); +} + +Status BasicBlockEnd(const ElementMetadata& metadata, const SimpleBlock& block, + Callback* callback) { + return callback->OnSimpleBlockEnd(metadata, block); +} + +} // namespace + +template <typename T> +Status BasicBlockParser<T>::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == kUnknownElementSize || metadata.size < 5) { + return Status(Status::kInvalidElementSize); + } + + *this = {}; + frame_metadata_.parent_element = metadata; + + return Status(Status::kOkCompleted); +} + +template <typename T> +Status BasicBlockParser<T>::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + Status status; + std::uint64_t local_num_bytes_read; + + while (true) { + switch (state_) { + case State::kReadingHeader: { + status = header_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + header_bytes_read_ += local_num_bytes_read; + if (!status.completed_ok()) { + return status; + } + value_.track_number = header_parser_.value().track_number; + value_.timecode = header_parser_.value().timecode; + + std::uint8_t flags = header_parser_.value().flags; + value_.is_visible = (0x08 & flags) == 0; + value_.lacing = static_cast<Lacing>(flags & 0x06); + ParseBasicBlockFlags(flags, &value_); + + if (value_.lacing == Lacing::kNone) { + value_.num_frames = 1; + state_ = State::kGettingAction; + } else { + state_ = State::kReadingLaceCount; + } + continue; + } + + case State::kReadingLaceCount: { + assert(lace_sizes_.empty()); + std::uint8_t lace_count; + status = ReadByte(reader, &lace_count); + if (!status.completed_ok()) { + return status; + } + ++*num_bytes_read; + ++header_bytes_read_; + // Lace count is stored as (count - 1). + value_.num_frames = lace_count + 1; + state_ = State::kGettingAction; + continue; + } + + case State::kGettingAction: { + Action action = Action::kRead; + status = BasicBlockBegin(frame_metadata_.parent_element, value_, + callback, &action); + if (!status.completed_ok()) { + return status; + } + if (action == Action::kSkip) { + state_ = State::kSkipping; + } else if (value_.lacing == Lacing::kNone || value_.num_frames == 1) { + state_ = State::kValidatingSize; + } else if (value_.lacing == Lacing::kXiph) { + state_ = State::kReadingXiphLaceSizes; + } else if (value_.lacing == Lacing::kEbml) { + state_ = State::kReadingFirstEbmlLaceSize; + } else { + state_ = State::kCalculatingFixedLaceSizes; + } + continue; + } + + case State::kReadingXiphLaceSizes: + assert(value_.num_frames > 0); + while (static_cast<int>(lace_sizes_.size()) < value_.num_frames - 1) { + std::uint8_t byte; + do { + status = ReadByte(reader, &byte); + if (!status.completed_ok()) { + return status; + } + ++*num_bytes_read; + ++header_bytes_read_; + xiph_lace_size_ += byte; + } while (byte == 255); + + lace_sizes_.push_back(xiph_lace_size_); + xiph_lace_size_ = 0; + } + state_ = State::kValidatingSize; + continue; + + case State::kReadingFirstEbmlLaceSize: + assert(value_.num_frames > 0); + assert(lace_sizes_.empty()); + status = uint_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + header_bytes_read_ += local_num_bytes_read; + if (!status.completed_ok()) { + return status; + } + lace_sizes_.push_back(uint_parser_.value()); + uint_parser_ = {}; + state_ = State::kReadingEbmlLaceSizes; + continue; + + case State::kReadingEbmlLaceSizes: + assert(value_.num_frames > 0); + assert(!lace_sizes_.empty()); + while (static_cast<int>(lace_sizes_.size()) < value_.num_frames - 1) { + status = uint_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + header_bytes_read_ += local_num_bytes_read; + if (!status.completed_ok()) { + return status; + } + constexpr std::uint64_t one = 1; // Prettier than a static_cast. + std::uint64_t offset = + (one << (uint_parser_.encoded_length() * 7 - 1)) - 1; + lace_sizes_.push_back(lace_sizes_.back() + uint_parser_.value() - + offset); + uint_parser_ = {}; + } + state_ = State::kValidatingSize; + continue; + + case State::kCalculatingFixedLaceSizes: { + assert(value_.num_frames > 0); + assert(lace_sizes_.empty()); + if (header_bytes_read_ >= frame_metadata_.parent_element.size) { + return Status(Status::kInvalidElementValue); + } + std::uint64_t laced_data_size = + frame_metadata_.parent_element.size - header_bytes_read_; + std::uint64_t frame_size = laced_data_size / value_.num_frames; + if (laced_data_size % value_.num_frames != 0) { + return Status(Status::kInvalidElementValue); + } + lace_sizes_.resize(value_.num_frames, frame_size); + frame_metadata_.position = + frame_metadata_.parent_element.position + header_bytes_read_; + frame_metadata_.size = frame_size; + state_ = State::kReadingFrames; + continue; + } + + case State::kValidatingSize: { + std::uint64_t sum = std::accumulate( + lace_sizes_.begin(), lace_sizes_.end(), header_bytes_read_); + if (sum >= frame_metadata_.parent_element.size) { + return Status(Status::kInvalidElementValue); + } + lace_sizes_.push_back(frame_metadata_.parent_element.size - sum); + frame_metadata_.position = frame_metadata_.parent_element.position + + frame_metadata_.parent_element.header_size + + header_bytes_read_; + frame_metadata_.size = lace_sizes_[0]; + state_ = State::kReadingFrames; + continue; + } + + case State::kSkipping: + do { + // Skip the remaining part of the header and all of the frames. + status = reader->Skip( + frame_metadata_.parent_element.size - header_bytes_read_, + &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + header_bytes_read_ += local_num_bytes_read; + } while (status.code == Status::kOkPartial); + return status; + + case State::kReadingFrames: + assert(value_.num_frames > 0); + assert(static_cast<int>(lace_sizes_.size()) == value_.num_frames); + for (; current_lace_ < lace_sizes_.size(); ++current_lace_) { + const std::uint64_t original = lace_sizes_[current_lace_]; + status = callback->OnFrame(frame_metadata_, reader, + &lace_sizes_[current_lace_]); + *num_bytes_read += original - lace_sizes_[current_lace_]; + if (!status.completed_ok()) { + return status; + } + assert(lace_sizes_[current_lace_] == 0); + if (current_lace_ + 1 < lace_sizes_.size()) { + frame_metadata_.position += frame_metadata_.size; + frame_metadata_.size = lace_sizes_[current_lace_ + 1]; + } + } + state_ = State::kDone; + continue; + + case State::kDone: + return BasicBlockEnd(frame_metadata_.parent_element, value_, callback); + } + } +} + +template <typename T> +bool BasicBlockParser<T>::WasSkipped() const { + return state_ == State::kSkipping; +} + +template class BasicBlockParser<Block>; +template class BasicBlockParser<SimpleBlock>; + +} // namespace webm diff --git a/webm_parser/src/block_parser.h b/webm_parser/src/block_parser.h new file mode 100644 index 0000000..a906826 --- /dev/null +++ b/webm_parser/src/block_parser.h @@ -0,0 +1,126 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BLOCK_PARSER_H_ +#define SRC_BLOCK_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <type_traits> +#include <vector> + +#include "src/block_header_parser.h" +#include "src/element_parser.h" +#include "src/var_int_parser.h" +#include "webm/callback.h" +#include "webm/dom_types.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses Block and SimpleBlock elements. It is recommended to use the +// BlockParser and SimpleBlockParser aliases. +// Spec reference: +// http://matroska.org/technical/specs/index.html#Block +// http://matroska.org/technical/specs/index.html#SimpleBlock +// http://www.webmproject.org/docs/container/#SimpleBlock +// http://www.webmproject.org/docs/container/#Block +// http://matroska.org/technical/specs/index.html#block_structure +// http://matroska.org/technical/specs/index.html#simpleblock_structure +template <typename T> +class BasicBlockParser : public ElementParser { + static_assert(std::is_same<T, Block>::value || + std::is_same<T, SimpleBlock>::value, + "T must be Block or SimpleBlock"); + + public: + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + bool WasSkipped() const override; + + // Gets the parsed block header information. The frames are not included. This + // must not be called until the parse has been successfully completed. + const T& value() const { + assert(state_ == State::kDone); + return value_; + } + + // Gets the parsed block header information. The frames are not included. This + // must not be called until the parse has been successfully completed. + T* mutable_value() { + assert(state_ == State::kDone); + return &value_; + } + + private: + // The number of header bytes read (header meaning everything before the + // frames). + std::uint64_t header_bytes_read_ = 0; + + // The parsed header value for the element. + T value_{}; + + // Metadata for the frame that is currently being read. + FrameMetadata frame_metadata_; + + // Parser for parsing header metadata that is common between Block and + // SimpleBlock. + BlockHeaderParser header_parser_; + + // Parser for parsing unsigned EBML variable-sized integers. + VarIntParser uint_parser_; + + // The current lace size when parsing Xiph lace sizes. + std::uint64_t xiph_lace_size_ = 0; + + // Lace (frame) sizes, where each entry represents the size of a frame. + std::vector<std::uint64_t> lace_sizes_; + + // The current index into lace_sizes_ for the current frame being read. + std::size_t current_lace_ = 0; + + // Parsing states for the finite-state machine. + enum class State { + /* clang-format off */ + // State Transitions to state When + kReadingHeader, // kGettingAction no lacing + // kReadingLaceCount yes lacing + kReadingLaceCount, // kGettingAction no errors + kGettingAction, // kSkipping action == skip + // kValidatingSize no lacing + // kReadingXiphLaceSizes xiph lacing + // kReadingFirstEbmlLaceSize ebml lacing + // kCalculatingFixedLaceSizes fixed lacing + kReadingXiphLaceSizes, // kValidatingSize all sizes read + kReadingFirstEbmlLaceSize, // kReadingEbmlLaceSizes first size read + kReadingEbmlLaceSizes, // kValidatingSize all sizes read + kCalculatingFixedLaceSizes, // kReadingFrames no errors + kValidatingSize, // kReadingFrames no errors + kSkipping, // No transitions from here (must call Init) + kReadingFrames, // kDone all frames read + kDone, // No transitions from here (must call Init) + /* clang-format on */ + }; + + // The current state of the parser. + State state_ = State::kReadingHeader; +}; + +extern template class BasicBlockParser<Block>; +extern template class BasicBlockParser<SimpleBlock>; + +using BlockParser = BasicBlockParser<Block>; +using SimpleBlockParser = BasicBlockParser<SimpleBlock>; + +} // namespace webm + +#endif // SRC_BLOCK_PARSER_H_ diff --git a/webm_parser/src/bool_parser.h b/webm_parser/src/bool_parser.h new file mode 100644 index 0000000..3d7f11e --- /dev/null +++ b/webm_parser/src/bool_parser.h @@ -0,0 +1,95 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BOOL_PARSER_H_ +#define SRC_BOOL_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/element_parser.h" +#include "src/parser_utils.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses a boolean from a byte stream. EBML does not have a boolean type, but +// the Matroska spec defines some unsigned integer elements that have a range of +// [0, 1]. The BoolParser parses these unsigned integer elements into +// true/false, and reports a Status::kInvalidElementValue error if the integer +// is outside of its permitted range. +class BoolParser : public ElementParser { + public: + explicit BoolParser(bool default_value = false) + : default_value_(default_value) {} + + Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) override { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + // Booleans are really just unsigned integers with a range limit of 0-1. + // Unsigned integers can't be encoded with more than 8 bytes. + if (metadata.size > 8) { + return Status(Status::kInvalidElementSize); + } + + size_ = num_bytes_remaining_ = static_cast<int>(metadata.size); + value_ = default_value_; + + return Status(Status::kOkCompleted); + } + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + std::uint64_t uint_value = 0; + const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader, + &uint_value, num_bytes_read); + num_bytes_remaining_ -= static_cast<int>(*num_bytes_read); + + // Only the last byte should have a value, and it should only be 0 or 1. + if ((num_bytes_remaining_ != 0 && uint_value != 0) || uint_value > 1) { + return Status(Status::kInvalidElementValue); + } + + if (size_ > 0) { + value_ = uint_value == 1; + } + + return status; + } + + // Gets the parsed bool. This must not be called until the parse had been + // successfully completed. + bool value() const { + assert(num_bytes_remaining_ == 0); + return value_; + } + + // Gets the parsed bool. This must not be called until the parse had been + // successfully completed. + bool* mutable_value() { + assert(num_bytes_remaining_ == 0); + return &value_; + } + + private: + bool value_; + bool default_value_; + int num_bytes_remaining_ = -1; + int size_; +}; + +} // namespace webm + +#endif // SRC_BOOL_PARSER_H_ diff --git a/webm_parser/src/buffer_reader.cc b/webm_parser/src/buffer_reader.cc new file mode 100644 index 0000000..51b0b01 --- /dev/null +++ b/webm_parser/src/buffer_reader.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/buffer_reader.h" + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <initializer_list> +#include <utility> +#include <vector> + +#include "webm/status.h" + +namespace webm { + +BufferReader::BufferReader(std::initializer_list<std::uint8_t> bytes) + : data_(bytes) {} + +BufferReader::BufferReader(const std::vector<std::uint8_t>& vector) + : data_(vector) {} + +BufferReader::BufferReader(std::vector<std::uint8_t>&& vector) + : data_(std::move(vector)) {} + +BufferReader::BufferReader(BufferReader&& other) + : data_(std::move(other.data_)), pos_(other.pos_) { + other.pos_ = 0; +} + +BufferReader& BufferReader::operator=(BufferReader&& other) { + if (this != &other) { + data_ = std::move(other.data_); + pos_ = other.pos_; + other.pos_ = 0; + } + return *this; +} + +BufferReader& BufferReader::operator=( + std::initializer_list<std::uint8_t> bytes) { + data_ = std::vector<std::uint8_t>(bytes); + pos_ = 0; + return *this; +} + +Status BufferReader::Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) { + assert(num_to_read > 0); + assert(buffer != nullptr); + assert(num_actually_read != nullptr); + + *num_actually_read = 0; + std::size_t expected = num_to_read; + + std::size_t num_remaining = data_.size() - pos_; + if (num_remaining == 0) { + return Status(Status::kEndOfFile); + } + + if (num_to_read > num_remaining) { + num_to_read = static_cast<std::size_t>(num_remaining); + } + + std::copy_n(data_.data() + pos_, num_to_read, buffer); + *num_actually_read = num_to_read; + pos_ += num_to_read; + + if (*num_actually_read != expected) { + return Status(Status::kOkPartial); + } + + return Status(Status::kOkCompleted); +} + +Status BufferReader::Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) { + assert(num_to_skip > 0); + assert(num_actually_skipped != nullptr); + + *num_actually_skipped = 0; + std::uint64_t expected = num_to_skip; + + std::size_t num_remaining = data_.size() - pos_; + if (num_remaining == 0) { + return Status(Status::kEndOfFile); + } + + if (num_to_skip > num_remaining) { + num_to_skip = static_cast<std::uint64_t>(num_remaining); + } + + *num_actually_skipped = num_to_skip; + pos_ += num_to_skip; + + if (*num_actually_skipped != expected) { + return Status(Status::kOkPartial); + } + + return Status(Status::kOkCompleted); +} + +std::uint64_t BufferReader::Position() const { return pos_; } + +} // namespace webm diff --git a/webm_parser/src/byte_parser.h b/webm_parser/src/byte_parser.h new file mode 100644 index 0000000..f2ed3cb --- /dev/null +++ b/webm_parser/src/byte_parser.h @@ -0,0 +1,144 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_BYTE_PARSER_H_ +#define SRC_BYTE_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <string> +#include <utility> +#include <vector> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses an EBML string (UTF-8 and ASCII) or binary element from a byte stream. +// Spec reference for string/binary elements: +// http://matroska.org/technical/specs/index.html#EBML_ex +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-element-types +template <typename T> +class ByteParser : public ElementParser { + public: + static_assert(std::is_same<T, std::vector<std::uint8_t>>::value || + std::is_same<T, std::string>::value, + "T must be std::vector<std::uint8_t> or std::string"); + + // Constructs a new parser which will use the given default_value as the + // value for the element if its size is zero. Defaults to the empty string + // or empty binary element (as the EBML spec indicates). + explicit ByteParser(T default_value = {}) + : default_value_(std::move(default_value)) {} + + ByteParser(ByteParser&&) = default; + ByteParser& operator=(ByteParser&&) = default; + + ByteParser(const ByteParser&) = delete; + ByteParser& operator=(const ByteParser&) = delete; + + Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) override { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == kUnknownElementSize) { + return Status(Status::kInvalidElementSize); + } + + if (metadata.size > std::numeric_limits<std::size_t>::max() || + metadata.size > value_.max_size()) { + return Status(Status::kNotEnoughMemory); + } + +#if WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT + // AFL and ASan just kill the process if too much memory is allocated, so + // let's cap the maximum size of the element. It's too easy for the fuzzer + // to make an element with a ridiculously huge size, and that just creates + // uninteresting false positives. + if (metadata.size > WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT) { + return Status(Status::kNotEnoughMemory); + } +#endif + + if (metadata.size == 0) { + value_ = default_value_; + total_read_ = default_value_.size(); + } else { + value_.resize(static_cast<std::size_t>(metadata.size)); + total_read_ = 0; + } + + return Status(Status::kOkCompleted); + } + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + if (total_read_ == value_.size()) { + return Status(Status::kOkCompleted); + } + + Status status; + do { + std::uint64_t local_num_bytes_read = 0; + std::uint8_t* buffer = + reinterpret_cast<std::uint8_t*>(&value_.front()) + total_read_; + std::size_t buffer_size = value_.size() - total_read_; + status = reader->Read(buffer_size, buffer, &local_num_bytes_read); + assert((status.completed_ok() && local_num_bytes_read == buffer_size) || + (status.ok() && local_num_bytes_read < buffer_size) || + (!status.ok() && local_num_bytes_read == 0)); + *num_bytes_read += local_num_bytes_read; + total_read_ += static_cast<std::size_t>(local_num_bytes_read); + } while (status.code == Status::kOkPartial); + + // UTF-8 and ASCII string elements can be padded with NUL characters at the + // end, which should be ignored. + if (std::is_same<T, std::string>::value && status.completed_ok()) { + while (!value_.empty() && value_.back() == '\0') { + value_.pop_back(); + } + } + + return status; + } + + // Gets the parsed value. This must not be called until the parse has been + // successfully completed. + const T& value() const { + assert(total_read_ >= value_.size()); + return value_; + } + + // Gets the parsed value. This must not be called until the parse has been + // successfully completed. + T* mutable_value() { + assert(total_read_ >= value_.size()); + return &value_; + } + + private: + T value_; + T default_value_; + std::size_t total_read_; +}; + +using StringParser = ByteParser<std::string>; +using BinaryParser = ByteParser<std::vector<std::uint8_t>>; + +} // namespace webm + +#endif // SRC_BYTE_PARSER_H_ diff --git a/webm_parser/src/callback.cc b/webm_parser/src/callback.cc new file mode 100644 index 0000000..cda30c8 --- /dev/null +++ b/webm_parser/src/callback.cc @@ -0,0 +1,155 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/callback.h" + +#include <cassert> + +namespace webm { + +Status Callback::OnElementBegin(const ElementMetadata& /* metadata */, + Action* action) { + assert(action != nullptr); + *action = Action::kRead; + return Status(Status::kOkCompleted); +} + +Status Callback::OnUnknownElement(const ElementMetadata& /* metadata */, + Reader* reader, + std::uint64_t* bytes_remaining) { + assert(reader != nullptr); + assert(bytes_remaining != nullptr); + return Skip(reader, bytes_remaining); +} + +Status Callback::OnEbml(const ElementMetadata& /* metadata */, + const Ebml& /* ebml */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnVoid(const ElementMetadata& /* metadata */, Reader* reader, + std::uint64_t* bytes_remaining) { + assert(reader != nullptr); + assert(bytes_remaining != nullptr); + return Skip(reader, bytes_remaining); +} + +Status Callback::OnSegmentBegin(const ElementMetadata& /* metadata */, + Action* action) { + assert(action != nullptr); + *action = Action::kRead; + return Status(Status::kOkCompleted); +} + +Status Callback::OnSeek(const ElementMetadata& /* metadata */, + const Seek& /* seek */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnInfo(const ElementMetadata& /* metadata */, + const Info& /* info */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnClusterBegin(const ElementMetadata& /* metadata */, + const Cluster& /* cluster */, Action* action) { + assert(action != nullptr); + *action = Action::kRead; + return Status(Status::kOkCompleted); +} + +Status Callback::OnSimpleBlockBegin(const ElementMetadata& /* metadata */, + const SimpleBlock& /* simple_block */, + Action* action) { + assert(action != nullptr); + *action = Action::kRead; + return Status(Status::kOkCompleted); +} + +Status Callback::OnSimpleBlockEnd(const ElementMetadata& /* metadata */, + const SimpleBlock& /* simple_block */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnBlockGroupBegin(const ElementMetadata& /* metadata */, + Action* action) { + assert(action != nullptr); + *action = Action::kRead; + return Status(Status::kOkCompleted); +} + +Status Callback::OnBlockBegin(const ElementMetadata& /* metadata */, + const Block& /* block */, Action* action) { + assert(action != nullptr); + *action = Action::kRead; + return Status(Status::kOkCompleted); +} + +Status Callback::OnBlockEnd(const ElementMetadata& /* metadata */, + const Block& /* block */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnBlockGroupEnd(const ElementMetadata& /* metadata */, + const BlockGroup& /* block_group */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnFrame(const FrameMetadata& /* metadata */, Reader* reader, + std::uint64_t* bytes_remaining) { + assert(reader != nullptr); + assert(bytes_remaining != nullptr); + return Skip(reader, bytes_remaining); +} + +Status Callback::OnClusterEnd(const ElementMetadata& /* metadata */, + const Cluster& /* cluster */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnTrackEntry(const ElementMetadata& /* metadata */, + const TrackEntry& /* track_entry */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnCuePoint(const ElementMetadata& /* metadata */, + const CuePoint& /* cue_point */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnEditionEntry(const ElementMetadata& /* metadata */, + const EditionEntry& /* edition_entry */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnTag(const ElementMetadata& /* metadata */, + const Tag& /* tag */) { + return Status(Status::kOkCompleted); +} + +Status Callback::OnSegmentEnd(const ElementMetadata& /* metadata */) { + return Status(Status::kOkCompleted); +} + +Status Callback::Skip(Reader* reader, std::uint64_t* bytes_remaining) { + assert(reader != nullptr); + assert(bytes_remaining != nullptr); + + if (*bytes_remaining == 0) + return Status(Status::kOkCompleted); + + Status status; + do { + std::uint64_t num_actually_skipped; + status = reader->Skip(*bytes_remaining, &num_actually_skipped); + *bytes_remaining -= num_actually_skipped; + } while (status.code == Status::kOkPartial); + + return status; +} + +} // namespace webm diff --git a/webm_parser/src/chapter_atom_parser.h b/webm_parser/src/chapter_atom_parser.h new file mode 100644 index 0000000..653f59f --- /dev/null +++ b/webm_parser/src/chapter_atom_parser.h @@ -0,0 +1,42 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CHAPTER_ATOM_PARSER_H_ +#define SRC_CHAPTER_ATOM_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/chapter_display_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#ChapterAtom +// http://www.webmproject.org/docs/container/#ChapterAtom +class ChapterAtomParser : public MasterValueParser<ChapterAtom> { + public: + explicit ChapterAtomParser(std::size_t max_recursive_depth = 25) + : MasterValueParser<ChapterAtom>( + MakeChild<UnsignedIntParser>(Id::kChapterUid, &ChapterAtom::uid), + MakeChild<StringParser>(Id::kChapterStringUid, + &ChapterAtom::string_uid), + MakeChild<UnsignedIntParser>(Id::kChapterTimeStart, + &ChapterAtom::time_start), + MakeChild<UnsignedIntParser>(Id::kChapterTimeEnd, + &ChapterAtom::time_end), + MakeChild<ChapterDisplayParser>(Id::kChapterDisplay, + &ChapterAtom::displays), + MakeChild<ChapterAtomParser>(Id::kChapterAtom, &ChapterAtom::atoms, + max_recursive_depth)) {} +}; + +} // namespace webm + +#endif // SRC_CHAPTER_ATOM_PARSER_H_ diff --git a/webm_parser/src/chapter_display_parser.h b/webm_parser/src/chapter_display_parser.h new file mode 100644 index 0000000..4c4cfd2 --- /dev/null +++ b/webm_parser/src/chapter_display_parser.h @@ -0,0 +1,34 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CHAPTER_DISPLAY_PARSER_H_ +#define SRC_CHAPTER_DISPLAY_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#ChapterDisplay +// http://www.webmproject.org/docs/container/#ChapterDisplay +class ChapterDisplayParser : public MasterValueParser<ChapterDisplay> { + public: + ChapterDisplayParser() + : MasterValueParser<ChapterDisplay>( + MakeChild<StringParser>(Id::kChapString, &ChapterDisplay::string), + MakeChild<StringParser>(Id::kChapLanguage, + &ChapterDisplay::languages), + MakeChild<StringParser>(Id::kChapCountry, + &ChapterDisplay::countries)) {} +}; + +} // namespace webm + +#endif // SRC_CHAPTER_DISPLAY_PARSER_H_ diff --git a/webm_parser/src/chapters_parser.h b/webm_parser/src/chapters_parser.h new file mode 100644 index 0000000..2863cf9 --- /dev/null +++ b/webm_parser/src/chapters_parser.h @@ -0,0 +1,28 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CHAPTERS_PARSER_H_ +#define SRC_CHAPTERS_PARSER_H_ + +#include "src/edition_entry_parser.h" +#include "src/master_parser.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Chapters +// http://www.webmproject.org/docs/container/#Chapters +class ChaptersParser : public MasterParser { + public: + ChaptersParser() + : MasterParser(MakeChild<EditionEntryParser>(Id::kEditionEntry)) {} +}; + +} // namespace webm + +#endif // SRC_CHAPTERS_PARSER_H_ diff --git a/webm_parser/src/cluster_parser.h b/webm_parser/src/cluster_parser.h new file mode 100644 index 0000000..35cba92 --- /dev/null +++ b/webm_parser/src/cluster_parser.h @@ -0,0 +1,48 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CLUSTER_PARSER_H_ +#define SRC_CLUSTER_PARSER_H_ + +#include "src/block_group_parser.h" +#include "src/block_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Cluster +// http://www.webmproject.org/docs/container/#Cluster +class ClusterParser : public MasterValueParser<Cluster> { + public: + ClusterParser() + : MasterValueParser<Cluster>( + MakeChild<UnsignedIntParser>(Id::kTimecode, &Cluster::timecode), + MakeChild<UnsignedIntParser>(Id::kPrevSize, + &Cluster::previous_size), + MakeChild<SimpleBlockParser>(Id::kSimpleBlock, + &Cluster::simple_blocks) + .UseAsStartEvent(), + MakeChild<BlockGroupParser>(Id::kBlockGroup, &Cluster::block_groups) + .UseAsStartEvent()) {} + + protected: + Status OnParseStarted(Callback* callback, Action* action) override { + return callback->OnClusterBegin(metadata(Id::kCluster), value(), action); + } + + Status OnParseCompleted(Callback* callback) override { + return callback->OnClusterEnd(metadata(Id::kCluster), value()); + } +}; + +} // namespace webm + +#endif // SRC_CLUSTER_PARSER_H_ diff --git a/webm_parser/src/colour_parser.h b/webm_parser/src/colour_parser.h new file mode 100644 index 0000000..e05f14f --- /dev/null +++ b/webm_parser/src/colour_parser.h @@ -0,0 +1,55 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_COLOUR_PARSER_H_ +#define SRC_COLOUR_PARSER_H_ + +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "src/mastering_metadata_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Colour +// http://www.webmproject.org/docs/container/#Colour +class ColourParser : public MasterValueParser<Colour> { + public: + ColourParser() + : MasterValueParser<Colour>( + MakeChild<IntParser<MatrixCoefficients>>( + Id::kMatrixCoefficients, &Colour::matrix_coefficients), + MakeChild<UnsignedIntParser>(Id::kBitsPerChannel, + &Colour::bits_per_channel), + MakeChild<UnsignedIntParser>(Id::kChromaSubsamplingHorz, + &Colour::chroma_subsampling_x), + MakeChild<UnsignedIntParser>(Id::kChromaSubsamplingVert, + &Colour::chroma_subsampling_y), + MakeChild<UnsignedIntParser>(Id::kCbSubsamplingHorz, + &Colour::cb_subsampling_x), + MakeChild<UnsignedIntParser>(Id::kCbSubsamplingVert, + &Colour::cb_subsampling_y), + MakeChild<UnsignedIntParser>(Id::kChromaSitingHorz, + &Colour::chroma_siting_x), + MakeChild<UnsignedIntParser>(Id::kChromaSitingVert, + &Colour::chroma_siting_y), + MakeChild<IntParser<Range>>(Id::kRange, &Colour::range), + MakeChild<IntParser<TransferCharacteristics>>( + Id::kTransferCharacteristics, + &Colour::transfer_characteristics), + MakeChild<IntParser<Primaries>>(Id::kPrimaries, &Colour::primaries), + MakeChild<UnsignedIntParser>(Id::kMaxCll, &Colour::max_cll), + MakeChild<UnsignedIntParser>(Id::kMaxFall, &Colour::max_fall), + MakeChild<MasteringMetadataParser>(Id::kMasteringMetadata, + &Colour::mastering_metadata)) {} +}; + +} // namespace webm + +#endif // SRC_COLOUR_PARSER_H_ diff --git a/webm_parser/src/content_enc_aes_settings_parser.h b/webm_parser/src/content_enc_aes_settings_parser.h new file mode 100644 index 0000000..27f8635 --- /dev/null +++ b/webm_parser/src/content_enc_aes_settings_parser.h @@ -0,0 +1,32 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CONTENT_ENC_AES_SETTINGS_PARSER_H_ +#define SRC_CONTENT_ENC_AES_SETTINGS_PARSER_H_ + +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://www.webmproject.org/docs/webm-encryption/#42-new-matroskawebm-elements +class ContentEncAesSettingsParser + : public MasterValueParser<ContentEncAesSettings> { + public: + ContentEncAesSettingsParser() + : MasterValueParser<ContentEncAesSettings>( + MakeChild<IntParser<AesSettingsCipherMode>>( + Id::kAesSettingsCipherMode, + &ContentEncAesSettings::aes_settings_cipher_mode)) {} +}; + +} // namespace webm + +#endif // SRC_CONTENT_ENC_AES_SETTINGS_PARSER_H_ diff --git a/webm_parser/src/content_encoding_parser.h b/webm_parser/src/content_encoding_parser.h new file mode 100644 index 0000000..4fccfe7 --- /dev/null +++ b/webm_parser/src/content_encoding_parser.h @@ -0,0 +1,38 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CONTENT_ENCODING_PARSER_H_ +#define SRC_CONTENT_ENCODING_PARSER_H_ + +#include "src/content_encryption_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#ContentEncoding +// http://www.webmproject.org/docs/container/#ContentEncoding +class ContentEncodingParser : public MasterValueParser<ContentEncoding> { + public: + ContentEncodingParser() + : MasterValueParser<ContentEncoding>( + MakeChild<UnsignedIntParser>(Id::kContentEncodingOrder, + &ContentEncoding::order), + MakeChild<UnsignedIntParser>(Id::kContentEncodingScope, + &ContentEncoding::scope), + MakeChild<IntParser<ContentEncodingType>>(Id::kContentEncodingType, + &ContentEncoding::type), + MakeChild<ContentEncryptionParser>(Id::kContentEncryption, + &ContentEncoding::encryption)) {} +}; + +} // namespace webm + +#endif // SRC_CONTENT_ENCODING_PARSER_H_ diff --git a/webm_parser/src/content_encodings_parser.h b/webm_parser/src/content_encodings_parser.h new file mode 100644 index 0000000..0933fda --- /dev/null +++ b/webm_parser/src/content_encodings_parser.h @@ -0,0 +1,30 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CONTENT_ENCODINGS_PARSER_H_ +#define SRC_CONTENT_ENCODINGS_PARSER_H_ + +#include "src/content_encoding_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#ContentEncodings +// http://www.webmproject.org/docs/container/#ContentEncodings +class ContentEncodingsParser : public MasterValueParser<ContentEncodings> { + public: + ContentEncodingsParser() + : MasterValueParser<ContentEncodings>(MakeChild<ContentEncodingParser>( + Id::kContentEncoding, &ContentEncodings::encodings)) {} +}; + +} // namespace webm + +#endif // SRC_CONTENT_ENCODINGS_PARSER_H_ diff --git a/webm_parser/src/content_encryption_parser.h b/webm_parser/src/content_encryption_parser.h new file mode 100644 index 0000000..46ec2e7 --- /dev/null +++ b/webm_parser/src/content_encryption_parser.h @@ -0,0 +1,37 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CONTENT_ENCRYPTION_PARSER_H_ +#define SRC_CONTENT_ENCRYPTION_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/content_enc_aes_settings_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#ContentEncryption +// http://www.webmproject.org/docs/container/#ContentEncryption +class ContentEncryptionParser : public MasterValueParser<ContentEncryption> { + public: + ContentEncryptionParser() + : MasterValueParser<ContentEncryption>( + MakeChild<IntParser<ContentEncAlgo>>(Id::kContentEncAlgo, + &ContentEncryption::algorithm), + MakeChild<BinaryParser>(Id::kContentEncKeyId, + &ContentEncryption::key_id), + MakeChild<ContentEncAesSettingsParser>( + Id::kContentEncAesSettings, &ContentEncryption::aes_settings)) { + } +}; + +} // namespace webm + +#endif // SRC_CONTENT_ENCRYPTION_PARSER_H_ diff --git a/webm_parser/src/cue_point_parser.h b/webm_parser/src/cue_point_parser.h new file mode 100644 index 0000000..a8ebe4d --- /dev/null +++ b/webm_parser/src/cue_point_parser.h @@ -0,0 +1,38 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CUE_POINT_PARSER_H_ +#define SRC_CUE_POINT_PARSER_H_ + +#include "src/cue_track_positions_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#CuePoint +// http://www.webmproject.org/docs/container/#CuePoint +class CuePointParser : public MasterValueParser<CuePoint> { + public: + CuePointParser() + : MasterValueParser<CuePoint>( + MakeChild<UnsignedIntParser>(Id::kCueTime, &CuePoint::time), + MakeChild<CueTrackPositionsParser>( + Id::kCueTrackPositions, &CuePoint::cue_track_positions)) {} + + protected: + Status OnParseCompleted(Callback* callback) override { + return callback->OnCuePoint(metadata(Id::kCuePoint), value()); + } +}; + +} // namespace webm + +#endif // SRC_CUE_POINT_PARSER_H_ diff --git a/webm_parser/src/cue_track_positions_parser.h b/webm_parser/src/cue_track_positions_parser.h new file mode 100644 index 0000000..eede78e --- /dev/null +++ b/webm_parser/src/cue_track_positions_parser.h @@ -0,0 +1,39 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CUE_TRACK_POSITIONS_PARSER_H_ +#define SRC_CUE_TRACK_POSITIONS_PARSER_H_ + +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#CueTrackPositions +// http://www.webmproject.org/docs/container/#CueTrackPositions +class CueTrackPositionsParser : public MasterValueParser<CueTrackPositions> { + public: + CueTrackPositionsParser() + : MasterValueParser<CueTrackPositions>( + MakeChild<UnsignedIntParser>(Id::kCueTrack, + &CueTrackPositions::track), + MakeChild<UnsignedIntParser>(Id::kCueClusterPosition, + &CueTrackPositions::cluster_position), + MakeChild<UnsignedIntParser>(Id::kCueRelativePosition, + &CueTrackPositions::relative_position), + MakeChild<UnsignedIntParser>(Id::kCueDuration, + &CueTrackPositions::duration), + MakeChild<UnsignedIntParser>(Id::kCueBlockNumber, + &CueTrackPositions::block_number)) {} +}; + +} // namespace webm + +#endif // SRC_CUE_TRACK_POSITIONS_PARSER_H_ diff --git a/webm_parser/src/cues_parser.h b/webm_parser/src/cues_parser.h new file mode 100644 index 0000000..7316b82 --- /dev/null +++ b/webm_parser/src/cues_parser.h @@ -0,0 +1,27 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_CUES_PARSER_H_ +#define SRC_CUES_PARSER_H_ + +#include "src/cue_point_parser.h" +#include "src/master_parser.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Cues +// http://www.webmproject.org/docs/container/#Cues +class CuesParser : public MasterParser { + public: + CuesParser() : MasterParser(MakeChild<CuePointParser>(Id::kCuePoint)) {} +}; + +} // namespace webm + +#endif // SRC_CUES_PARSER_H_ diff --git a/webm_parser/src/date_parser.cc b/webm_parser/src/date_parser.cc new file mode 100644 index 0000000..1eb8801 --- /dev/null +++ b/webm_parser/src/date_parser.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/date_parser.h" + +#include <cassert> +#include <cstdint> +#include <limits> + +#include "src/parser_utils.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#EBML_ex +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-element-types +DateParser::DateParser(std::int64_t default_value) + : default_value_(default_value) {} + +Status DateParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size != 0 && metadata.size != 8) { + return Status(Status::kInvalidElementSize); + } + + num_bytes_remaining_ = static_cast<int>(metadata.size); + + // The meaning of a 0-byte element is still being debated. EBML says the value + // is zero; Matroska says it's the default according to whatever the document + // spec says. Neither specifies what a 0-byte mandatory element means. I've + // asked about this on the Matroska mailing list. I'm going to assume a 0-byte + // mandatory element should be treated the same as a 0-byte optional element, + // meaning that they both get their default value (which may be some value + // other than zero). This applies to all non-master-elements (not just dates). + // This parser is an EBML-level parser, and so will default to a value of + // zero. The Matroska-level parser can reset this default value to something + // else after parsing (as needed). + // See: + // https://github.com/Matroska-Org/ebml-specification/pull/17 + // http://lists.matroska.org/pipermail/matroska-devel/2015-October/004866.html + if (metadata.size == 0) { + value_ = default_value_; + } else { + value_ = 0; + } + + return Status(Status::kOkCompleted); +} + +Status DateParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader, + &value_, num_bytes_read); + num_bytes_remaining_ -= static_cast<int>(*num_bytes_read); + + return status; +} + +} // namespace webm diff --git a/webm_parser/src/date_parser.h b/webm_parser/src/date_parser.h new file mode 100644 index 0000000..8038be3 --- /dev/null +++ b/webm_parser/src/date_parser.h @@ -0,0 +1,64 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_DATE_PARSER_H_ +#define SRC_DATE_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses an EBML date from a byte stream. EBML dates are signed integer values +// that represent the offset, in nanoseconds, from 2001-01-01T00:00:00.00 UTC. +class DateParser : public ElementParser { + public: + // Constructs a new parser which will use the given default_value as the + // value for the element if its size is zero. Defaults to the value zero (as + // the EBML spec indicates). + explicit DateParser(std::int64_t default_value = 0); + + DateParser(DateParser&&) = default; + DateParser& operator=(DateParser&&) = default; + + DateParser(const DateParser&) = delete; + DateParser& operator=(const DateParser&) = delete; + + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed date. This must not be called until the parse had been + // successfully completed. + std::int64_t value() const { + assert(num_bytes_remaining_ == 0); + return value_; + } + + // Gets the parsed date. This must not be called until the parse had been + // successfully completed. + std::int64_t* mutable_value() { + assert(num_bytes_remaining_ == 0); + return &value_; + } + + private: + std::int64_t value_; + std::int64_t default_value_; + int num_bytes_remaining_ = -1; +}; + +} // namespace webm + +#endif // SRC_DATE_PARSER_H_ diff --git a/webm_parser/src/ebml_parser.h b/webm_parser/src/ebml_parser.h new file mode 100644 index 0000000..c5de155 --- /dev/null +++ b/webm_parser/src/ebml_parser.h @@ -0,0 +1,48 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_EBML_PARSER_H_ +#define SRC_EBML_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec references: +// http://matroska.org/technical/specs/index.html#EBML +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-header-elements +// http://www.webmproject.org/docs/container/#EBML +class EbmlParser : public MasterValueParser<Ebml> { + public: + EbmlParser() + : MasterValueParser<Ebml>( + MakeChild<UnsignedIntParser>(Id::kEbmlVersion, &Ebml::ebml_version), + MakeChild<UnsignedIntParser>(Id::kEbmlReadVersion, + &Ebml::ebml_read_version), + MakeChild<UnsignedIntParser>(Id::kEbmlMaxIdLength, + &Ebml::ebml_max_id_length), + MakeChild<UnsignedIntParser>(Id::kEbmlMaxSizeLength, + &Ebml::ebml_max_size_length), + MakeChild<StringParser>(Id::kDocType, &Ebml::doc_type), + MakeChild<UnsignedIntParser>(Id::kDocTypeVersion, + &Ebml::doc_type_version), + MakeChild<UnsignedIntParser>(Id::kDocTypeReadVersion, + &Ebml::doc_type_read_version)) {} + + protected: + Status OnParseCompleted(Callback* callback) override { + return callback->OnEbml(metadata(Id::kEbml), value()); + } +}; + +} // namespace webm + +#endif // SRC_EBML_PARSER_H_ diff --git a/webm_parser/src/edition_entry_parser.h b/webm_parser/src/edition_entry_parser.h new file mode 100644 index 0000000..1a82228 --- /dev/null +++ b/webm_parser/src/edition_entry_parser.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_EDITION_ENTRY_PARSER_H_ +#define SRC_EDITION_ENTRY_PARSER_H_ + +#include "src/chapter_atom_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#EditionEntry +// http://www.webmproject.org/docs/container/#EditionEntry +class EditionEntryParser : public MasterValueParser<EditionEntry> { + public: + EditionEntryParser() + : MasterValueParser<EditionEntry>(MakeChild<ChapterAtomParser>( + Id::kChapterAtom, &EditionEntry::atoms)) {} + + protected: + Status OnParseCompleted(Callback* callback) override { + return callback->OnEditionEntry(metadata(Id::kEditionEntry), value()); + } +}; + +} // namespace webm + +#endif // SRC_EDITION_ENTRY_PARSER_H_ diff --git a/webm_parser/src/element_parser.h b/webm_parser/src/element_parser.h new file mode 100644 index 0000000..5e202e8 --- /dev/null +++ b/webm_parser/src/element_parser.h @@ -0,0 +1,102 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_ELEMENT_PARSER_H_ +#define SRC_ELEMENT_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/ancestory.h" +#include "src/parser.h" +#include "webm/callback.h" +#include "webm/element.h" + +namespace webm { + +// Parses an element from a WebM byte stream. Objects that implement this +// interface are expected to be used as follows in order to parse the specific +// WebM element that they are designed to handle. +// +// Reader* reader = ...; // Create some Reader. +// Callback* callback = ...; // Create some Callback. +// +// ElementMetadata metadata = { +// id, // Element parsed from the reader. +// header_size, // The number of bytes used to encode the id and size. +// size_in_bytes, // The number of bytes in the element body. +// position, // The position of the element (starting at the ID). +// }; +// +// std::uint64_t max_size = ...; // Some upper bound on this element's size. +// ElementParser* parser = ...; // Create some parser capable of handling +// // elements that match id. +// +// Status status = parser->Init(metadata, max_size); +// if (!status.completed_ok()) { +// // An error occurred. See status.code for the reason. +// } else { +// do { +// std::uint64_t num_bytes_read = 0; +// status = parser->Feed(callback, reader, &num_bytes_read); +// } while (status.code == Status::kOkPartial); +// +// if (status.completed_ok()) { +// // Parsing successfully completed. +// } else { +// // An error occurred. If status.code is a parsing error (see status.h for +// // errors that are considered parsing errors), do not call Feed again; +// // parsing has already failed and further progress can't be made. If +// // status.code is not a parsing error (i.e. Status::kWouldBlock), then +// // Feed may be called again to attempt resuming parsing. +// } +// } +class ElementParser : public Parser { + public: + // Initializes the parser and prepares it for parsing its element. Returns + // Status::kOkCompleted if successful. Must not return Status::kOkPartial (it + // is not resumable). metadata is the metadata associated with this element. + // max_size must be <= metadata.size (unless metadata.size is + // kUnknownElementSize). + virtual Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) = 0; + + // Initializes the parser after a seek was done and prepares it for parsing. + // The reader is now at the position of the child element indicated by + // child_metadata, whose ancestory is child_ancestory. The child element for + // this parser is the first element in child_ancestory, or if that is empty, + // then child_metadata itself. If the child is not a valid child of this + // parser, then a debug assertion is made (because that indicates a bug). + virtual void InitAfterSeek(const Ancestory& /* child_ancestory */, + const ElementMetadata& /* child_metadata */) { + assert(false); + } + + // Returns true and sets metadata if this parser read too far and read the + // element metadata for an element that is not its child. This may happen, for + // example, when an element with unknown size is being read (because its end + // is considered the first element that is not a valid child, so it must read + // further to detect this). If this did not happen and false is returned, then + // metadata will not be modified. metadata must not be null. + virtual bool GetCachedMetadata(ElementMetadata* metadata) { + assert(metadata != nullptr); + + return false; + } + + // Returns true if this parser skipped the element instead of fully parsing + // it. This will be true if the user requested a kSkip action from the + // Callback in Feed(). This method should only be called after Feed() has + // returned kOkCompleted. If the element was skipped, do not try to access its + // value; it has no meaningful value and doing so will likely result in an + // assertion failing. + virtual bool WasSkipped() const { return false; } +}; + +} // namespace webm + +#endif // SRC_ELEMENT_PARSER_H_ diff --git a/webm_parser/src/file_reader.cc b/webm_parser/src/file_reader.cc new file mode 100644 index 0000000..0921abe --- /dev/null +++ b/webm_parser/src/file_reader.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/file_reader.h" + +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <limits> +#include <memory> + +#include "webm/status.h" + +namespace webm { + +FileReader::FileReader(FILE* file) : file_(file) { assert(file); } + +FileReader::FileReader(FileReader&& other) + : file_(std::move(other.file_)), position_(other.position_) { + other.position_ = 0; +} + +FileReader& FileReader::operator=(FileReader&& other) { + if (this != &other) { + file_ = std::move(other.file_); + position_ = other.position_; + other.position_ = 0; + } + return *this; +} + +Status FileReader::Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) { + assert(num_to_read > 0); + assert(buffer != nullptr); + assert(num_actually_read != nullptr); + + if (file_ == nullptr) { + *num_actually_read = 0; + return Status(Status::kEndOfFile); + } + + std::size_t actual = + std::fread(static_cast<void*>(buffer), 1, num_to_read, file_.get()); + *num_actually_read = static_cast<std::uint64_t>(actual); + position_ += *num_actually_read; + + if (actual == 0) { + return Status(Status::kEndOfFile); + } + + if (actual == num_to_read) { + return Status(Status::kOkCompleted); + } else { + return Status(Status::kOkPartial); + } +} + +Status FileReader::Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) { + assert(num_to_skip > 0); + assert(num_actually_skipped != nullptr); + + *num_actually_skipped = 0; + + if (file_ == nullptr) { + return Status(Status::kEndOfFile); + } + + // Try seeking forward first. + long seek_offset = std::numeric_limits<long>::max(); // NOLINT + if (num_to_skip < static_cast<unsigned long>(seek_offset)) { // NOLINT + seek_offset = static_cast<long>(num_to_skip); // NOLINT + } + // TODO(mjbshaw): Use fseeko64/_fseeki64 if available. + if (!std::fseek(file_.get(), seek_offset, SEEK_CUR)) { + *num_actually_skipped = static_cast<std::uint64_t>(seek_offset); + position_ += static_cast<std::uint64_t>(seek_offset); + if (static_cast<unsigned long>(seek_offset) == num_to_skip) { // NOLINT + return Status(Status::kOkCompleted); + } else { + return Status(Status::kOkPartial); + } + } + std::clearerr(file_.get()); + + // Seeking doesn't work on things like pipes, so if seeking failed then fall + // back to reading the data into a junk buffer. + std::size_t actual = 0; + do { + std::uint8_t junk[1024]; + std::size_t num_to_read = sizeof(junk); + if (num_to_skip < num_to_read) { + num_to_read = static_cast<std::size_t>(num_to_skip); + } + + std::size_t actual = + std::fread(static_cast<void*>(junk), 1, num_to_read, file_.get()); + *num_actually_skipped += static_cast<std::uint64_t>(actual); + position_ += static_cast<std::uint64_t>(actual); + num_to_skip -= static_cast<std::uint64_t>(actual); + } while (actual > 0 && num_to_skip > 0); + + if (*num_actually_skipped == 0) { + return Status(Status::kEndOfFile); + } + + if (num_to_skip == 0) { + return Status(Status::kOkCompleted); + } else { + return Status(Status::kOkPartial); + } +} + +std::uint64_t FileReader::Position() const { return position_; } + +} // namespace webm diff --git a/webm_parser/src/float_parser.cc b/webm_parser/src/float_parser.cc new file mode 100644 index 0000000..fc46860 --- /dev/null +++ b/webm_parser/src/float_parser.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/float_parser.h" + +#include <cassert> +#include <cstdint> +#include <cstring> +#include <limits> + +#include "src/parser_utils.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +FloatParser::FloatParser(double default_value) + : default_value_(default_value) {} + +Status FloatParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == 0) { + value_ = default_value_; + } else if (metadata.size == 4 || metadata.size == 8) { + uint64_value_ = 0; + } else { + return Status(Status::kInvalidElementSize); + } + + num_bytes_remaining_ = static_cast<int>(metadata.size); + use_32_bits_ = metadata.size == 4; + + return Status(Status::kOkCompleted); +} + +Status FloatParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + if (num_bytes_remaining_ == 0) { + return Status(Status::kOkCompleted); + } + + const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader, + &uint64_value_, num_bytes_read); + num_bytes_remaining_ -= static_cast<int>(*num_bytes_read); + + if (num_bytes_remaining_ == 0) { + if (use_32_bits_) { + static_assert(std::numeric_limits<float>::is_iec559, + "Your compiler does not support 32-bit IEC 559/IEEE 754 " + "floating point types"); + std::uint32_t uint32_value = static_cast<std::uint32_t>(uint64_value_); + float float32_value; + std::memcpy(&float32_value, &uint32_value, 4); + value_ = float32_value; + } else { + static_assert(std::numeric_limits<double>::is_iec559, + "Your compiler does not support 64-bit IEC 559/IEEE 754 " + "floating point types"); + std::memcpy(&value_, &uint64_value_, 8); + } + } + + return status; +} + +} // namespace webm diff --git a/webm_parser/src/float_parser.h b/webm_parser/src/float_parser.h new file mode 100644 index 0000000..1412b54 --- /dev/null +++ b/webm_parser/src/float_parser.h @@ -0,0 +1,65 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_FLOAT_PARSER_H_ +#define SRC_FLOAT_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses an EBML float from a byte stream. +class FloatParser : public ElementParser { + public: + // Constructs a new parser which will use the given default_value as the + // value for the element if its size is zero. Defaults to the value zero (as + // the EBML spec indicates). + explicit FloatParser(double default_value = 0.0); + + FloatParser(FloatParser&&) = default; + FloatParser& operator=(FloatParser&&) = default; + + FloatParser(const FloatParser&) = delete; + FloatParser& operator=(const FloatParser&) = delete; + + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed float. This must not be called until the parse had been + // successfully completed. + double value() const { + assert(num_bytes_remaining_ == 0); + return value_; + } + + // Gets the parsed float. This must not be called until the parse had been + // successfully completed. + double* mutable_value() { + assert(num_bytes_remaining_ == 0); + return &value_; + } + + private: + double value_; + double default_value_; + std::uint64_t uint64_value_; + int num_bytes_remaining_ = -1; + bool use_32_bits_; +}; + +} // namespace webm + +#endif // SRC_FLOAT_PARSER_H_ diff --git a/webm_parser/src/id_element_parser.cc b/webm_parser/src/id_element_parser.cc new file mode 100644 index 0000000..849ad47 --- /dev/null +++ b/webm_parser/src/id_element_parser.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/id_element_parser.h" + +#include <cassert> +#include <cstdint> + +#include "src/element_parser.h" +#include "src/parser_utils.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +Status IdElementParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == 0 || metadata.size > 4) { + return Status(Status::kInvalidElementSize); + } + + num_bytes_remaining_ = static_cast<int>(metadata.size); + value_ = static_cast<Id>(0); + + return Status(Status::kOkCompleted); +} + +Status IdElementParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader, + &value_, num_bytes_read); + num_bytes_remaining_ -= static_cast<int>(*num_bytes_read); + + return status; +} + +} // namespace webm diff --git a/webm_parser/src/id_element_parser.h b/webm_parser/src/id_element_parser.h new file mode 100644 index 0000000..9b64089 --- /dev/null +++ b/webm_parser/src/id_element_parser.h @@ -0,0 +1,56 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_ID_ELEMENT_PARSER_H_ +#define SRC_ID_ELEMENT_PARSER_H_ + +#include <cstdint> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +class IdElementParser : public ElementParser { + public: + IdElementParser() = default; + + IdElementParser(IdElementParser&&) = default; + IdElementParser& operator=(IdElementParser&&) = default; + + IdElementParser(const IdElementParser&) = delete; + IdElementParser& operator=(const IdElementParser&) = delete; + + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed Id. This must not be called until the parse had been + // successfully completed. + Id value() const { + assert(num_bytes_remaining_ == 0); + return value_; + } + + // Gets the parsed Id. This must not be called until the parse had been + // successfully completed. + Id* mutable_value() { + assert(num_bytes_remaining_ == 0); + return &value_; + } + + private: + Id value_; + int num_bytes_remaining_ = -1; +}; + +} // namespace webm + +#endif // SRC_ID_ELEMENT_PARSER_H_ diff --git a/webm_parser/src/id_parser.cc b/webm_parser/src/id_parser.cc new file mode 100644 index 0000000..292a13c --- /dev/null +++ b/webm_parser/src/id_parser.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/id_parser.h" + +#include <cassert> +#include <cstdint> + +#include "src/bit_utils.h" +#include "src/parser_utils.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +Status IdParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + assert(num_bytes_remaining_ != 0); + + *num_bytes_read = 0; + + // Spec references: + // http://matroska.org/technical/specs/index.html#EBML_ex + // https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#variable-size-integer + // https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#element-id + + // IDs are encoded like so (big-endian): + // 0b1xxx xxxx + // 0b01xx xxxx xxxx xxxx + // 0b001x xxxx xxxx xxxx xxxx xxxx + // 0b0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx + + if (num_bytes_remaining_ == -1) { + std::uint8_t first_byte; + const Status status = ReadByte(reader, &first_byte); + if (!status.completed_ok()) { + return status; + } + ++*num_bytes_read; + + // The marker bit is the first 1-bit. It indicates the length of the ID. + // If there is no marker bit in the first half-octet, this isn't a valid + // ID, since IDs can't be more than 4 octets in MKV/WebM. + if (!(first_byte & 0xf0)) { + return Status(Status::kInvalidElementId); + } + + num_bytes_remaining_ = CountLeadingZeros(first_byte); + + id_ = static_cast<Id>(first_byte); + } + + std::uint64_t local_num_bytes_read; + const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader, + &id_, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + num_bytes_remaining_ -= static_cast<int>(local_num_bytes_read); + + return status; +} + +Id IdParser::id() const { + assert(num_bytes_remaining_ == 0); + return id_; +} + +} // namespace webm diff --git a/webm_parser/src/id_parser.h b/webm_parser/src/id_parser.h new file mode 100644 index 0000000..7594094 --- /dev/null +++ b/webm_parser/src/id_parser.h @@ -0,0 +1,45 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_ID_PARSER_H_ +#define SRC_ID_PARSER_H_ + +#include <cstdint> + +#include "src/parser.h" +#include "webm/callback.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses an EBML ID from a byte stream. +class IdParser : public Parser { + public: + IdParser() = default; + IdParser(IdParser&&) = default; + IdParser& operator=(IdParser&&) = default; + + IdParser(const IdParser&) = delete; + IdParser& operator=(const IdParser&) = delete; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed ID. This must not be called until the parse had been + // successfully completed. + Id id() const; + + private: + int num_bytes_remaining_ = -1; + Id id_; +}; + +} // namespace webm + +#endif // SRC_ID_PARSER_H_ diff --git a/webm_parser/src/info_parser.h b/webm_parser/src/info_parser.h new file mode 100644 index 0000000..085c8ad --- /dev/null +++ b/webm_parser/src/info_parser.h @@ -0,0 +1,44 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_INFO_PARSER_H_ +#define SRC_INFO_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/date_parser.h" +#include "src/float_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Info +// http://www.webmproject.org/docs/container/#Info +class InfoParser : public MasterValueParser<Info> { + public: + InfoParser() + : MasterValueParser<Info>( + MakeChild<UnsignedIntParser>(Id::kTimecodeScale, + &Info::timecode_scale), + MakeChild<FloatParser>(Id::kDuration, &Info::duration), + MakeChild<DateParser>(Id::kDateUtc, &Info::date_utc), + MakeChild<StringParser>(Id::kTitle, &Info::title), + MakeChild<StringParser>(Id::kMuxingApp, &Info::muxing_app), + MakeChild<StringParser>(Id::kWritingApp, &Info::writing_app)) {} + + protected: + Status OnParseCompleted(Callback* callback) override { + return callback->OnInfo(metadata(Id::kInfo), value()); + } +}; + +} // namespace webm + +#endif // SRC_INFO_PARSER_H_ diff --git a/webm_parser/src/int_parser.h b/webm_parser/src/int_parser.h new file mode 100644 index 0000000..f0ed392 --- /dev/null +++ b/webm_parser/src/int_parser.h @@ -0,0 +1,121 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_INT_PARSER_H_ +#define SRC_INT_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <limits> +#include <type_traits> + +#include "src/element_parser.h" +#include "src/parser_utils.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses an EBML signed/unsigned int from a byte stream. +// Spec reference: +// http://matroska.org/technical/specs/index.html#EBML_ex +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#element-data-size +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-element-types +template <typename T> +class IntParser : public ElementParser { + public: + static_assert( + std::is_same<T, std::int64_t>::value || + std::is_same<T, std::uint64_t>::value || + (std::is_enum<T>::value && sizeof(T) == 8), + "T must be either std::int64_t, std::uint64_t, or a 64-bit enum"); + + // Constructs a new parser which will use the given default_value as the + // value for the element if its size is zero. Defaults to the value zero (as + // the EBML spec indicates). + explicit IntParser(T default_value = {}) : default_value_(default_value) {} + + IntParser(IntParser&&) = default; + IntParser& operator=(IntParser&&) = default; + + IntParser(const IntParser&) = delete; + IntParser& operator=(const IntParser&) = delete; + + Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) override { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + // Matroska requires integers to be 0-8 bytes in size. + if (metadata.size > 8) { + return Status(Status::kInvalidElementSize); + } + + size_ = num_bytes_remaining_ = static_cast<int>(metadata.size); + + if (metadata.size == 0) { + value_ = default_value_; + } else { + value_ = {}; + } + + return Status(Status::kOkCompleted); + } + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader, + &value_, num_bytes_read); + num_bytes_remaining_ -= static_cast<int>(*num_bytes_read); + + // Sign extend the integer if it's a negative value. EBML allows for + // negative integers to drop superfluous sign bytes (i.e. -1 can be encoded + // as 0xFF instead of 0xFFFFFFFFFFFFFFFF). + if (std::is_signed<T>::value && num_bytes_remaining_ == 0 && size_ > 0) { + std::uint64_t sign_bits = std::numeric_limits<std::uint64_t>::max() + << (8 * size_ - 1); + std::uint64_t unsigned_value = static_cast<std::uint64_t>(value_); + if (unsigned_value & sign_bits) { + value_ = static_cast<T>(unsigned_value | sign_bits); + } + } + + return status; + } + + // Gets the parsed int. This must not be called until the parse had been + // successfully completed. + T value() const { + assert(num_bytes_remaining_ == 0); + return value_; + } + + // Gets the parsed int. This must not be called until the parse had been + // successfully completed. + T* mutable_value() { + assert(num_bytes_remaining_ == 0); + return &value_; + } + + private: + T value_; + T default_value_; + int num_bytes_remaining_ = -1; + int size_; +}; + +using SignedIntParser = IntParser<std::int64_t>; +using UnsignedIntParser = IntParser<std::uint64_t>; + +} // namespace webm + +#endif // SRC_INT_PARSER_H_ diff --git a/webm_parser/src/istream_reader.cc b/webm_parser/src/istream_reader.cc new file mode 100644 index 0000000..6993fc0 --- /dev/null +++ b/webm_parser/src/istream_reader.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/istream_reader.h" + +#include <cassert> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <limits> +#include <memory> + +#include "webm/status.h" + +namespace webm { + +IstreamReader::IstreamReader(IstreamReader&& other) + : istream_(std::move(other.istream_)), position_(other.position_) { + other.position_ = 0; +} + +IstreamReader& IstreamReader::operator=(IstreamReader&& other) { + if (this != &other) { + istream_ = std::move(other.istream_); + position_ = other.position_; + other.position_ = 0; + } + return *this; +} + +Status IstreamReader::Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) { + assert(num_to_read > 0); + assert(buffer != nullptr); + assert(num_actually_read != nullptr); + + if (istream_ == nullptr) { + *num_actually_read = 0; + return Status(Status::kEndOfFile); + } + + using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; + constexpr std::streamsize streamsize_max = + std::numeric_limits<std::streamsize>::max(); + std::streamsize limited_num_to_read; + if (num_to_read > static_cast<unsigned_streamsize>(streamsize_max)) { + limited_num_to_read = streamsize_max; + } else { + limited_num_to_read = static_cast<std::streamsize>(num_to_read); + } + + istream_->read(reinterpret_cast<char*>(buffer), limited_num_to_read); + std::streamsize actual = istream_->gcount(); + *num_actually_read = static_cast<std::uint64_t>(actual); + position_ += *num_actually_read; + + if (actual == 0) { + return Status(Status::kEndOfFile); + } + + if (static_cast<std::size_t>(actual) == num_to_read) { + return Status(Status::kOkCompleted); + } else { + return Status(Status::kOkPartial); + } +} + +Status IstreamReader::Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) { + assert(num_to_skip > 0); + assert(num_actually_skipped != nullptr); + + *num_actually_skipped = 0; + if (istream_ == nullptr || !istream_->good()) { + return Status(Status::kEndOfFile); + } + + // Try seeking forward first. + using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; + constexpr std::streamsize streamsize_max = + std::numeric_limits<std::streamsize>::max(); + std::streamsize seek_offset; + if (num_to_skip > static_cast<unsigned_streamsize>(streamsize_max)) { + seek_offset = streamsize_max; + } else { + seek_offset = static_cast<std::streamsize>(num_to_skip); + } + if (istream_->seekg(seek_offset, std::ios_base::cur)) { + *num_actually_skipped = static_cast<std::uint64_t>(seek_offset); + position_ += static_cast<std::uint64_t>(seek_offset); + if (static_cast<std::uint64_t>(seek_offset) == num_to_skip) { + return Status(Status::kOkCompleted); + } else { + return Status(Status::kOkPartial); + } + } + istream_->clear(); + + // Seeking doesn't work on things like pipes, so if seeking failed then fall + // back to reading the data into a junk buffer. + std::size_t actual = 0; + do { + char junk[1024]; + std::streamsize num_to_read = static_cast<std::streamsize>(sizeof(junk)); + if (num_to_skip < static_cast<std::uint64_t>(num_to_read)) { + num_to_read = static_cast<std::streamsize>(num_to_skip); + } + + istream_->read(junk, num_to_read); + std::streamsize actual = istream_->gcount(); + *num_actually_skipped += static_cast<std::uint64_t>(actual); + position_ += static_cast<std::uint64_t>(actual); + num_to_skip -= static_cast<std::uint64_t>(actual); + } while (actual > 0 && num_to_skip > 0); + + if (*num_actually_skipped == 0) { + return Status(Status::kEndOfFile); + } + + if (num_to_skip == 0) { + return Status(Status::kOkCompleted); + } else { + return Status(Status::kOkPartial); + } +} + +std::uint64_t IstreamReader::Position() const { return position_; } + +} // namespace webm diff --git a/webm_parser/src/master_parser.cc b/webm_parser/src/master_parser.cc new file mode 100644 index 0000000..d8c3dba --- /dev/null +++ b/webm_parser/src/master_parser.cc @@ -0,0 +1,298 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/master_parser.h" + +#include <cassert> +#include <cstdint> +#include <limits> + +#include "src/element_parser.h" +#include "src/skip_callback.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#EBML_ex +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown +Status MasterParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + InitSetup(metadata.header_size, metadata.size, metadata.position); + + if (metadata.size != kUnknownElementSize) { + max_size_ = metadata.size; + } else { + max_size_ = max_size; + } + + if (metadata.size == 0) { + state_ = State::kEndReached; + } else { + state_ = State::kFirstReadOfChildId; + } + + return Status(Status::kOkCompleted); +} + +void MasterParser::InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) { + InitSetup(kUnknownHeaderSize, kUnknownElementSize, kUnknownElementPosition); + max_size_ = std::numeric_limits<std::uint64_t>::max(); + + if (child_ancestory.empty()) { + child_metadata_ = child_metadata; + auto iter = parsers_.find(child_metadata_.id); + assert(iter != parsers_.end()); + child_parser_ = iter->second.get(); + state_ = State::kGettingAction; + } else { + child_metadata_.id = child_ancestory.id(); + child_metadata_.header_size = kUnknownHeaderSize; + child_metadata_.size = kUnknownElementSize; + child_metadata_.position = kUnknownElementPosition; + + auto iter = parsers_.find(child_metadata_.id); + assert(iter != parsers_.end()); + child_parser_ = iter->second.get(); + child_parser_->InitAfterSeek(child_ancestory.next(), child_metadata); + state_ = State::kReadingChildBody; + } +} + +Status MasterParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + Callback* const original_callback = callback; + + SkipCallback skip_callback; + if (action_ == Action::kSkip) { + callback = &skip_callback; + } + + Status status; + std::uint64_t local_num_bytes_read; + while (true) { + switch (state_) { + case State::kFirstReadOfChildId: { + // This separate case for the first read of the child ID is needed to + // avoid potential bugs where calling Feed() twice in a row on an + // unsized element at the end of the stream would return + // Status::kOkCompleted instead of Status::kEndOfFile (since we convert + // Status::kEndOfFile to Status::kOkCompleted when EOF is hit for an + // unsized element after its children have been fully parsed). Once + // the ID parser consumes > 0 bytes, this state must be exited. + assert(child_parser_ == nullptr); + assert(my_size_ == kUnknownElementSize || total_bytes_read_ < my_size_); + child_metadata_.position = reader->Position(); + child_metadata_.header_size = 0; + status = id_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + total_bytes_read_ += local_num_bytes_read; + child_metadata_.header_size += + static_cast<std::uint32_t>(local_num_bytes_read); + if (status.code == Status::kEndOfFile && + my_size_ == kUnknownElementSize && local_num_bytes_read == 0) { + state_ = State::kEndReached; + } else if (!status.ok()) { + if (local_num_bytes_read > 0) { + state_ = State::kFinishingReadingChildId; + } + return status; + } else if (status.completed_ok()) { + state_ = State::kReadingChildSize; + } else { + state_ = State::kFinishingReadingChildId; + } + continue; + } + + case State::kFinishingReadingChildId: { + assert(child_parser_ == nullptr); + assert(my_size_ == kUnknownElementSize || total_bytes_read_ < my_size_); + status = id_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + total_bytes_read_ += local_num_bytes_read; + child_metadata_.header_size += + static_cast<std::uint32_t>(local_num_bytes_read); + if (!status.completed_ok()) { + return status; + } + state_ = State::kReadingChildSize; + continue; + } + + case State::kReadingChildSize: { + assert(child_parser_ == nullptr); + assert(total_bytes_read_ > 0); + status = size_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + total_bytes_read_ += local_num_bytes_read; + child_metadata_.header_size += + static_cast<std::uint32_t>(local_num_bytes_read); + if (!status.completed_ok()) { + return status; + } + child_metadata_.id = id_parser_.id(); + child_metadata_.size = size_parser_.size(); + state_ = State::kValidatingChildSize; + continue; + } + + case State::kValidatingChildSize: { + assert(child_parser_ == nullptr); + + std::uint64_t byte_count = total_bytes_read_; + if (child_metadata_.size != kUnknownElementSize) { + byte_count += child_metadata_.size; + } + + std::uint64_t byte_cap = max_size_; + // my_size_ is <= max_size_ if it's known, so pick the smaller value. + if (my_size_ != kUnknownElementSize) { + byte_cap = my_size_; + } + + if (byte_count > byte_cap) { + return Status(Status::kElementOverflow); + } + + auto iter = parsers_.find(child_metadata_.id); + bool unknown_child = iter == parsers_.end(); + + if (my_size_ == kUnknownElementSize && unknown_child) { + // The end of an unsized master element is considered to be the first + // instance of an element that isn't a known/valid child element. + has_cached_metadata_ = true; + state_ = State::kEndReached; + continue; + } else if (unknown_child && + child_metadata_.size == kUnknownElementSize) { + // We can't skip or otherwise handle unknown elements with an unknown + // size. + return Status(Status::kIndefiniteUnknownElement); + } + if (unknown_child) { + child_parser_ = &unknown_parser_; + } else { + child_parser_ = iter->second.get(); + } + state_ = State::kGettingAction; + continue; + } + + case State::kGettingAction: { + assert(child_parser_ != nullptr); + status = callback->OnElementBegin(child_metadata_, &action_); + if (!status.completed_ok()) { + return status; + } + + if (action_ == Action::kSkip) { + callback = &skip_callback; + if (child_metadata_.size != kUnknownElementSize) { + child_parser_ = &skip_parser_; + } + } + state_ = State::kInitializingChildParser; + continue; + } + + case State::kInitializingChildParser: { + assert(child_parser_ != nullptr); + status = + child_parser_->Init(child_metadata_, max_size_ - total_bytes_read_); + if (!status.completed_ok()) { + return status; + } + state_ = State::kReadingChildBody; + continue; + } + + case State::kReadingChildBody: { + assert(child_parser_ != nullptr); + status = child_parser_->Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + total_bytes_read_ += local_num_bytes_read; + if (!status.completed_ok()) { + return status; + } + state_ = State::kChildFullyParsed; + continue; + } + + case State::kChildFullyParsed: { + assert(child_parser_ != nullptr); + std::uint64_t byte_cap = max_size_; + // my_size_ is <= max_size_ if it's known, so pick the smaller value. + if (my_size_ != kUnknownElementSize) { + byte_cap = my_size_; + } + + if (total_bytes_read_ > byte_cap) { + return Status(Status::kElementOverflow); + } else if (total_bytes_read_ == byte_cap) { + state_ = State::kEndReached; + continue; + } + + if (child_parser_->GetCachedMetadata(&child_metadata_)) { + state_ = State::kValidatingChildSize; + } else { + state_ = State::kFirstReadOfChildId; + } + PrepareForNextChild(); + callback = original_callback; + continue; + } + + case State::kEndReached: { + return Status(Status::kOkCompleted); + } + } + } +} + +bool MasterParser::GetCachedMetadata(ElementMetadata* metadata) { + assert(metadata != nullptr); + + if (has_cached_metadata_) { + *metadata = child_metadata_; + } + return has_cached_metadata_; +} + +void MasterParser::InitSetup(std::uint32_t header_size, + std::uint64_t size_in_bytes, + std::uint64_t position) { + PrepareForNextChild(); + header_size_ = header_size; + my_size_ = size_in_bytes; + my_position_ = position; + total_bytes_read_ = 0; + has_cached_metadata_ = false; +} + +void MasterParser::PrepareForNextChild() { + // Do not reset child_metadata_ here. + id_parser_ = {}; + size_parser_ = {}; + child_parser_ = nullptr; + action_ = Action::kRead; +} + +} // namespace webm diff --git a/webm_parser/src/master_parser.h b/webm_parser/src/master_parser.h new file mode 100644 index 0000000..2000167 --- /dev/null +++ b/webm_parser/src/master_parser.h @@ -0,0 +1,227 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_MASTER_PARSER_H_ +#define SRC_MASTER_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <functional> +#include <memory> +#include <type_traits> +#include <unordered_map> +#include <utility> + +#include "src/element_parser.h" +#include "src/id_parser.h" +#include "src/size_parser.h" +#include "src/skip_parser.h" +#include "src/unknown_parser.h" +#include "src/void_parser.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// A general purpose parser for EBML master elements. +// +// For example, if a document specification defines a Foo master element that +// has two boolean children (Bar and Baz), then a FooParser capable of parsing +// the Foo master element could be defined as follows: +// +// struct FooParser : public MasterParser { +// FooParser() +// : MasterParser(MakeChild<BoolParser>(Id::kBar), +// MakeChild<BoolParser>(Id::kBaz)) {} +// }; +// +// See the MasterValueParser for an alternative class for parsing master +// elements into a data structure. +class MasterParser : public ElementParser { + public: + // Constructs a new MasterParser that uses the given + // {Id, std::unique_ptr<ElementParser>} pairs to map child IDs to the + // appropriate parser/handler. Each argument must be of type + // std::pair<Id, std::unique_ptr<ElementParser>>. If a parser is not + // explicitly provided for Id::kVoid, a VoidParser will automatically be used + // for it. + // + // Initializer lists don't support move-only types (i.e. std::unique_ptr), so + // instead a variadic template is used. + template <typename... T> + explicit MasterParser(T&&... parser_pairs) { + // Prefer an odd reserve size. This makes libc++ use a prime number for the + // bucket count. Otherwise, if it happens to be a power of 2, then libc++ + // will use a power-of-2 bucket count (and since Matroska EBML IDs have low + // entropy in the low bits, there will be a lot of collisions). libstdc++ + // always prefers a prime bucket count. I'm not sure how MSVC or others are + // implemented, but this shouldn't adversely affect them even if they are + // implemented differently. Add one to the count because we'll likely need + // to insert a parser for Id::kVoid. + parsers_.reserve((sizeof...(T) + 1) | 1); + + // This dummy initializer list is just used to force the parameter pack to + // be expanded, which turns the expression into a for-each "loop" that + // inserts each argument into the map. + auto dummy = {0, (InsertParser(std::forward<T>(parser_pairs)), 0)...}; + (void)dummy; // Silence unused variable warning. + + if (parsers_.find(Id::kVoid) == parsers_.end()) { + InsertParser(MakeChild<VoidParser>(Id::kVoid)); + } + } + + MasterParser(const MasterParser&) = delete; + MasterParser& operator=(const MasterParser&) = delete; + + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + void InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + bool GetCachedMetadata(ElementMetadata* metadata) override; + + std::uint32_t header_size() const { return header_size_; } + + // Gets the size of this element. May be called before the parse is fully + // complete (but only after Init() has already been called and successfully + // returned). + std::uint64_t size() const { return my_size_; } + + // Gets absolute byte position of the start of the element in the byte stream. + // May be called before the parse is fully complete (but only after Init() has + // already been called and successfully returned). + std::uint64_t position() const { return my_position_; } + + // Gets the metadata for the child that is currently being parsed. This may + // only be called while the child's body (not its header information like ID + // and size) is being parsed. + const ElementMetadata& child_metadata() const { + assert(state_ == State::kValidatingChildSize || + state_ == State::kGettingAction || + state_ == State::kInitializingChildParser || + state_ == State::kReadingChildBody); + return child_metadata_; + } + + protected: + // Allocates a new parser of type T, forwarding args to the constructor, and + // creates a std::pair<Id, std::unique_ptr<ElementParser>> using the given id + // and the allocated parser. + template <typename T, typename... Args> + static std::pair<Id, std::unique_ptr<ElementParser>> MakeChild( + Id id, Args&&... args) { + std::unique_ptr<ElementParser> ptr(new T(std::forward<Args>(args)...)); + return std::pair<Id, std::unique_ptr<ElementParser>>(id, std::move(ptr)); + } + + private: + // Parsing states for the finite-state machine. + enum class State { + /* clang-format off */ + // State Transitions to state When + kFirstReadOfChildId, // kFinishingReadingChildId size(id) > 1 + // kReadingChildSize size(id) == 1 + // kEndReached EOF + kFinishingReadingChildId, // kReadingChildSize done + kReadingChildSize, // kValidatingChildSize done + kValidatingChildSize, // kGettingAction done + // kEndReached unknown id & unsized + kGettingAction, // kInitializingChildParser done + kInitializingChildParser, // kReadingChildBody done + kReadingChildBody, // kChildFullyParsed child parse done + kChildFullyParsed, // kValidatingChildSize cached metadata + // kFirstReadOfChildId read < my_size_ + // kEndReached read == my_size_ + kEndReached, // No transitions from here (must call Init) + /* clang-format on */ + }; + + using StdHashId = std::hash<std::underlying_type<Id>::type>; + + // Hash functor for hashing Id enums for storage in std::unordered_map. + struct IdHash : StdHashId { + // Type aliases for conforming to the std::hash interface. + using argument_type = Id; + using result_type = StdHashId::result_type; + + // Returns the hash of the given id. + result_type operator()(argument_type id) const { + return StdHashId::operator()(static_cast<StdHashId::argument_type>(id)); + } + }; + + // The parser for parsing element Ids. + IdParser id_parser_; + + // The parser for parsing element sizes. + SizeParser size_parser_; + + // Metadata for the child element that is currently being parsed. + ElementMetadata child_metadata_; + + // Maps child IDs to the appropriate parser that can handle that child. + std::unordered_map<Id, std::unique_ptr<ElementParser>, IdHash> parsers_; + + // The parser that is used to parse unknown children. + UnknownParser unknown_parser_; + + // The parser that is used to skip over children. + SkipParser skip_parser_; + + // The parser that is being used to parse the current child. This must be null + // or a pointer in parsers_. + ElementParser* child_parser_; + + // The current parsing action for the child that is currently being parsed. + Action action_ = Action::kRead; + + // The current state of the parser. + State state_; + + std::uint32_t header_size_; + + // The size of this element. + std::uint64_t my_size_; + + std::uint64_t my_position_; + + std::uint64_t max_size_; + + // The total number of bytes read by this parser. + std::uint64_t total_bytes_read_; + + // Set to true if parsing has completed and this parser consumed an extra + // element header (ID and size) that wasn't from a child. + bool has_cached_metadata_ = false; + + // Inserts the parser into the parsers_ map and asserts it is the only parser + // registers to parse the corresponding Id. + template <typename T> + void InsertParser(T&& parser) { + bool inserted = parsers_.insert(std::forward<T>(parser)).second; + (void)inserted; // Silence unused variable warning. + assert(inserted); // Make sure there aren't duplicates. + } + + // Common initialization logic for Init/InitAfterseek. + void InitSetup(std::uint32_t header_size, std::uint64_t size_in_bytes, + std::uint64_t position); + + // Resets the internal parsers in preparation for parsing the next child. + void PrepareForNextChild(); +}; + +} // namespace webm + +#endif // SRC_MASTER_PARSER_H_ diff --git a/webm_parser/src/master_value_parser.h b/webm_parser/src/master_value_parser.h new file mode 100644 index 0000000..2a02a3f --- /dev/null +++ b/webm_parser/src/master_value_parser.h @@ -0,0 +1,533 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_MASTER_VALUE_PARSER_H_ +#define SRC_MASTER_VALUE_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <functional> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "src/master_parser.h" +#include "src/recursive_parser.h" +#include "src/skip_callback.h" +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses Master elements from an EBML stream, storing child values in a +// structure of type T. This class differs from MasterParser in that +// MasterParser does not collect the parsed data into an object that can then be +// retrieved. +// +// For example, consider the following Foo object, which represents a master +// element that contains two booleans: +// +// struct Foo { +// Element<bool> bar; +// Element<bool> baz; +// }; +// +// A FooParser implemented via MasterParser, like below, could be used to parse +// the master element and the boolean children, but the boolean values could not +// be retrieved from the parser. +// +// struct FooParser : public MasterParser { +// FooParser() +// : MasterParser({Id::kBar, new BoolParser}, // std::pair<*> types +// {Id::kBaz, new BoolParser}) {} // omitted for brevity. +// }; +// +// However, if FooParser is implemented via MasterValueParser<Foo>, then the +// boolean values will be parsed into a Foo object that can be retrieved from +// FooParser via its value() and mutable_value() methods. +// +// struct FooParser : public MasterValueParser<Foo> { +// FooParser() +// : MasterValueParser(MakeChild<BoolParser>(Id::kBar, &Foo::bar), +// MakeChild<BoolParser>(Id::kBaz, &Foo::baz)) {} +// }; +template <typename T> +class MasterValueParser : public ElementParser { + public: + Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) override { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + PreInit(); + + const Status status = master_parser_.Init(metadata, max_size); + if (!status.completed_ok()) { + return status; + } + + return status; + } + + void InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) override { + PreInit(); + started_done_ = true; + master_parser_.InitAfterSeek(child_ancestory, child_metadata); + } + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + if (!parse_complete_) { + // TODO(mjbshaw): just call Reader::Skip if element's size is known and + // action is skip. + SkipCallback skip_callback; + if (action_ == Action::kSkip) { + callback = &skip_callback; + } + + Status status = master_parser_.Feed(callback, reader, num_bytes_read); + // Check if we've artificially injected an error code, and if so, switch + // into skipping mode. + if (status.code == Status::kSwitchToSkip) { + assert(started_done_); + assert(action_ == Action::kSkip); + callback = &skip_callback; + std::uint64_t local_num_bytes_read; + status = master_parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + } + if (!status.completed_ok()) { + return status; + } + parse_complete_ = true; + } + + if (!started_done_) { + Status status = OnParseStarted(callback, &action_); + if (!status.completed_ok()) { + return status; + } + started_done_ = true; + } + + if (action_ != Action::kSkip) { + return OnParseCompleted(callback); + } + + return Status(Status::kOkCompleted); + } + + bool GetCachedMetadata(ElementMetadata* metadata) override { + return master_parser_.GetCachedMetadata(metadata); + } + + bool WasSkipped() const override { return action_ == Action::kSkip; } + + const T& value() const { return value_; } + + T* mutable_value() { return &value_; } + + protected: + // Users and subclasses are not meant to use the Tag* classes; they're an + // internal implementation detail. + // A tag that will cause the internal ChildParser to use the parsed child as a + // start event. + struct TagUseAsStart {}; + // A tag that will cause the internal ChildParser to call OnChildParsed once + // it has been fully parsed. + struct TagNotifyOnParseComplete {}; + + // A factory that will create a std::pair<Id, std::unique_ptr<ElementParser>>. + // Users and subclasses are not meant to use this class directly, as it is an + // internal implementation detail of this class. Subclasses should use + // MakeChild instead of using this class directly. + template <typename Parser, typename Value, typename... Tags> + class SingleChildFactory { + public: + constexpr SingleChildFactory(Id id, Element<Value> T::*member) + : id_(id), member_(member) {} + + // Builds a std::pair<Id, std::unique_ptr<ElementParser>>. The parent + // pointer must be a pointer to the MasterValueParser that is being + // constructed. The given value pointer must be the pointer to the fully + // constructed MasterValueParser::value_ object. + std::pair<Id, std::unique_ptr<ElementParser>> BuildParser( + MasterValueParser* parent, T* value) { + assert(parent != nullptr); + assert(value != nullptr); + + Element<Value>* child_member = &(value->*member_); + auto lambda = [child_member](Parser* parser) { + child_member->Set(std::move(*parser->mutable_value()), true); + }; + return {id_, MakeChildParser<Parser, Value, Tags...>( + parent, std::move(lambda), child_member)}; + } + + // If called, OnParseStarted will be called on the parent element when this + // particular element is encountered. + constexpr SingleChildFactory<Parser, Value, TagUseAsStart, Tags...> + UseAsStartEvent() const { + return {id_, member_}; + } + + // If called, OnChildParsed will be called on the parent element when this + // particular element is fully parsed. + constexpr SingleChildFactory<Parser, Value, TagNotifyOnParseComplete, + Tags...> + NotifyOnParseComplete() const { + return {id_, member_}; + } + + private: + Id id_; + Element<Value> T::*member_; + }; + + template <typename Parser, typename Value, typename... Tags> + class RepeatedChildFactory { + public: + constexpr RepeatedChildFactory(Id id, + std::vector<Element<Value>> T::*member) + : id_(id), member_(member) {} + + // Builds a std::pair<Id, std::unique_ptr<ElementParser>>. The parent + // pointer must be a pointer to the MasterValueParser that is being + // constructed. The given value pointer must be the pointer to the fully + // constructed MasterValueParser::value_ object. + std::pair<Id, std::unique_ptr<ElementParser>> BuildParser( + MasterValueParser* parent, T* value) { + assert(parent != nullptr); + assert(value != nullptr); + + std::vector<Element<Value>>* child_member = &(value->*member_); + auto lambda = [child_member](Parser* parser) { + if (child_member->size() == 1 && !child_member->front().is_present()) { + child_member->clear(); + } + child_member->emplace_back(std::move(*parser->mutable_value()), true); + }; + return {id_, MakeChildParser<Parser, Value, Tags...>( + parent, std::move(lambda), child_member)}; + } + + // If called, OnParseStarted will be called on the parent element when this + // particular element is encountered. + constexpr RepeatedChildFactory<Parser, Value, TagUseAsStart, Tags...> + UseAsStartEvent() const { + return {id_, member_}; + } + + // If called, OnChildParsed will be called on the parent element when this + // particular element is fully parsed. + constexpr RepeatedChildFactory<Parser, Value, TagNotifyOnParseComplete, + Tags...> + NotifyOnParseComplete() const { + return {id_, member_}; + } + + private: + Id id_; + std::vector<Element<Value>> T::*member_; + }; + + template <typename Parser, typename... Tags> + class RecursiveChildFactory { + public: + constexpr RecursiveChildFactory(Id id, std::vector<Element<T>> T::*member, + std::size_t max_recursion_depth) + : id_(id), member_(member), max_recursion_depth_(max_recursion_depth) {} + + // Builds a std::pair<Id, std::unique_ptr<ElementParser>>. The parent + // pointer must be a pointer to the MasterValueParser that is being + // constructed. The given value pointer must be the pointer to the fully + // constructed MasterValueParser::value_ object. + std::pair<Id, std::unique_ptr<ElementParser>> BuildParser( + MasterValueParser* parent, T* value) { + assert(parent != nullptr); + assert(value != nullptr); + + std::vector<Element<T>>* child_member = &(value->*member_); + auto lambda = [child_member](RecursiveParser<Parser>* parser) { + if (child_member->size() == 1 && !child_member->front().is_present()) { + child_member->clear(); + } + child_member->emplace_back(std::move(*parser->mutable_value()), true); + }; + + return {id_, std::unique_ptr<ElementParser>( + new ChildParser<RecursiveParser<Parser>, + decltype(lambda), Tags...>( + parent, std::move(lambda), max_recursion_depth_))}; + } + + // If called, OnParseStarted will be called on the parent element when this + // particular element is encountered. + constexpr RecursiveChildFactory<Parser, TagUseAsStart, Tags...> + UseAsStartEvent() const { + return {id_, member_, max_recursion_depth_}; + } + + // If called, OnChildParsed will be called on the parent element when this + // particular element is fully parsed. + constexpr RecursiveChildFactory<Parser, TagNotifyOnParseComplete, Tags...> + NotifyOnParseComplete() const { + return {id_, member_, max_recursion_depth_}; + } + + private: + Id id_; + std::vector<Element<T>> T::*member_; + std::size_t max_recursion_depth_; + }; + + // Constructs a new parser. Each argument must be a *ChildFactory, constructed + // from the MakeChild method. + template <typename... Args> + explicit MasterValueParser(Args&&... args) + : master_parser_(args.BuildParser(this, &value_)...) {} + + // Returns a factory that will produce a + // std::pair<Id, std::unique_ptr<ElementParser>>. When a child element of the + // given ID is encountered, a parser of type Parser will be used to parse it, + // and store its value in the member pointer when the parse is complete. The + // given default value will be used in the event that the child element has a + // zero size. This method is only meant to be used by subclasses to provide + // the necessary factories to the constructor. + template <typename Parser, typename Value> + static SingleChildFactory<Parser, Value> MakeChild( + Id id, Element<Value> T::*member) { + static_assert(std::is_base_of<ElementParser, Parser>::value, + "Parser must derive from ElementParser"); + static_assert(!std::is_base_of<MasterValueParser<T>, Parser>::value, + "Recursive elements should be contained in a std::vector"); + return SingleChildFactory<Parser, Value>(id, member); + } + + template <typename Parser, typename Value> + static RepeatedChildFactory<Parser, Value> MakeChild( + Id id, std::vector<Element<Value>> T::*member) { + static_assert(std::is_base_of<ElementParser, Parser>::value, + "Parser must derive from ElementParser"); + static_assert(!std::is_base_of<MasterValueParser<T>, Parser>::value, + "Recursive elements require a maximum recursion depth"); + return RepeatedChildFactory<Parser, Value>(id, member); + } + + template <typename Parser> + static RecursiveChildFactory<Parser> MakeChild( + Id id, std::vector<Element<T>> T::*member, + std::size_t max_recursion_depth) { + static_assert(std::is_base_of<MasterValueParser<T>, Parser>::value, + "Child must be recusrive to use maximum recursion depth"); + return RecursiveChildFactory<Parser>(id, member, max_recursion_depth); + } + + // Gets the metadata for this element, setting the EBML element ID to id. Only + // call after Init() has been called. + ElementMetadata metadata(Id id) const { + return {id, master_parser_.header_size(), master_parser_.size(), + master_parser_.position()}; + } + + // This method will be called once the element has been fully parsed, or a + // particular child element of interest (see UseAsStartEvent()) is + // encountered. By default it just sets *action to Action::kRead and returns + // Status::kOkCompleted. May be overridden (i.e. in order to call a Callback + // method). Returning anything other than Status::kOkCompleted will stop + // parsing and the status to be returned by Init or Feed (whichever originated + // the call to this method). In this case, resuming parsing will result in + // this method being called again. + virtual Status OnParseStarted(Callback* callback, Action* action) { + assert(callback != nullptr); + assert(action != nullptr); + *action = Action::kRead; + return Status(Status::kOkCompleted); + } + + // This method is the companion to OnParseStarted, and will only be called + // after OnParseStarted has already been called and parsing has completed. + // This will not be called if OnParseStarted set the action to Action::kSkip. + // By default it just returns Status::kOkCompleted. Returning anything other + // than Status::kOkCompleted will stop parsing and the status to be returned + // by Init or Feed (whichever originated the call to this method). In this + // case, resuming parsing will result in this method being called again. + virtual Status OnParseCompleted(Callback* callback) { + assert(callback != nullptr); + return Status(Status::kOkCompleted); + } + + // Returns true if the OnParseStarted method has already been called and has + // completed. + bool parse_started_event_completed() const { return started_done_; } + + // Derived classes may manually call OnParseStarted before calling Feed, in + // which case this method should be called to inform this class that + // OnParseStarted has already been called and it should not be called again. + void set_parse_started_event_completed_with_action(Action action) { + assert(!started_done_); + + action_ = action; + started_done_ = true; + } + + // This method will be called for each child element that has been fully + // parsed for which NotifyOnParseComplete() was requested. The provided + // metadata is for the child element that has just completed parsing. By + // default this method does nothing. + virtual void OnChildParsed(const ElementMetadata& /* metadata */) {} + + private: + T value_; + Action action_ = Action::kRead; + bool parse_complete_; + bool started_done_; + // master_parser_ must be after value_ to ensure correct initialization order. + MasterParser master_parser_; + + const ElementMetadata& child_metadata() const { + return master_parser_.child_metadata(); + } + + // Helper struct that will be std::true_type if Tag is in Tags, or + // std::false_type otherwise. + template <typename Tag, typename... Tags> + struct HasTag; + + // Base condition: Tags is empty, so it trivially does not contain Tag. + template <typename Tag> + struct HasTag<Tag> : std::false_type {}; + + // If the head of the Tags list is a different tag, skip it and check the + // remaining tags. + template <typename Tag, typename DifferentTag, typename... Tags> + struct HasTag<Tag, DifferentTag, Tags...> : HasTag<Tag, Tags...> {}; + + // If the head of the Tags list is the same as Tag, then we're done. + template <typename Tag, typename... Tags> + struct HasTag<Tag, Tag, Tags...> : std::true_type {}; + + template <typename Base, typename F, typename... Tags> + class ChildParser : public Base { + public: + using Base::WasSkipped; + + template <typename... Args> + explicit ChildParser(MasterValueParser* parent, F consume_element_value, + Args&&... base_args) + : Base(std::forward<Args>(base_args)...), + parent_(parent), + consume_element_value_(std::move(consume_element_value)) {} + + ChildParser() = delete; + ChildParser(const ChildParser&) = delete; + ChildParser& operator=(const ChildParser&) = delete; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + *num_bytes_read = 0; + + Status status = Prepare(callback); + if (!status.completed_ok()) { + return status; + } + + status = Base::Feed(callback, reader, num_bytes_read); + if (status.completed_ok() && parent_->action_ != Action::kSkip && + !WasSkipped()) { + consume_element_value_(this); + if (has_tag<TagNotifyOnParseComplete>()) { + parent_->OnChildParsed(parent_->child_metadata()); + } + } + return status; + } + + private: + MasterValueParser* parent_; + F consume_element_value_; + + Status Prepare(Callback* callback) { + if (has_tag<TagUseAsStart>() && !parent_->started_done_) { + const Status status = + parent_->OnParseStarted(callback, &parent_->action_); + if (!status.completed_ok()) { + return status; + } + parent_->started_done_ = true; + if (parent_->action_ == Action::kSkip) { + return Status(Status::kSwitchToSkip); + } + } + return Status(Status::kOkCompleted); + } + + template <typename Tag> + constexpr static bool has_tag() { + return MasterValueParser::HasTag<Tag, Tags...>::value; + } + }; + + // Returns a std::unique_ptr<ElementParser> that points to a ChildParser + // when the Parser's constructor does not take a Value parameter. + template <typename Parser, typename Value, typename... Tags, typename F> + static typename std::enable_if<!std::is_constructible<Parser, Value>::value, + std::unique_ptr<ElementParser>>::type + MakeChildParser(MasterValueParser* parent, F consume_element_value, ...) { + return std::unique_ptr<ElementParser>(new ChildParser<Parser, F, Tags...>( + parent, std::move(consume_element_value))); + } + + // Returns a std::unique_ptr<ElementParser> that points to a ChildParser + // when the Parser's constructor does take a Value parameter. + template <typename Parser, typename Value, typename... Tags, typename F> + static typename std::enable_if<std::is_constructible<Parser, Value>::value, + std::unique_ptr<ElementParser>>::type + MakeChildParser(MasterValueParser* parent, F consume_element_value, + const Element<Value>* default_value) { + return std::unique_ptr<ElementParser>(new ChildParser<Parser, F, Tags...>( + parent, std::move(consume_element_value), default_value->value())); + } + + // Returns a std::unique_ptr<ElementParser> that points to a ChildParser + // when the Parser's constructor does take a Value parameter. + template <typename Parser, typename Value, typename... Tags, typename F> + static typename std::enable_if<std::is_constructible<Parser, Value>::value, + std::unique_ptr<ElementParser>>::type + MakeChildParser(MasterValueParser* parent, F consume_element_value, + const std::vector<Element<Value>>* member) { + Value default_value{}; + if (!member->empty()) { + default_value = member->front().value(); + } + return std::unique_ptr<ElementParser>(new ChildParser<Parser, F, Tags...>( + parent, std::move(consume_element_value), std::move(default_value))); + } + + // Initializes object state. Call immediately before initializing + // master_parser_. + void PreInit() { + value_ = {}; + action_ = Action::kRead; + parse_complete_ = false; + started_done_ = false; + } +}; + +} // namespace webm + +#endif // SRC_MASTER_VALUE_PARSER_H_ diff --git a/webm_parser/src/mastering_metadata_parser.h b/webm_parser/src/mastering_metadata_parser.h new file mode 100644 index 0000000..db6179c --- /dev/null +++ b/webm_parser/src/mastering_metadata_parser.h @@ -0,0 +1,57 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_MASTERING_METADATA_PARSER_H_ +#define SRC_MASTERING_METADATA_PARSER_H_ + +#include "src/float_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#MasteringMetadata +// http://www.webmproject.org/docs/container/#MasteringMetadata +class MasteringMetadataParser : public MasterValueParser<MasteringMetadata> { + public: + MasteringMetadataParser() + : MasterValueParser<MasteringMetadata>( + MakeChild<FloatParser>( + Id::kPrimaryRChromaticityX, + &MasteringMetadata::primary_r_chromaticity_x), + MakeChild<FloatParser>( + Id::kPrimaryRChromaticityY, + &MasteringMetadata::primary_r_chromaticity_y), + MakeChild<FloatParser>( + Id::kPrimaryGChromaticityX, + &MasteringMetadata::primary_g_chromaticity_x), + MakeChild<FloatParser>( + Id::kPrimaryGChromaticityY, + &MasteringMetadata::primary_g_chromaticity_y), + MakeChild<FloatParser>( + Id::kPrimaryBChromaticityX, + &MasteringMetadata::primary_b_chromaticity_x), + MakeChild<FloatParser>( + Id::kPrimaryBChromaticityY, + &MasteringMetadata::primary_b_chromaticity_y), + MakeChild<FloatParser>( + Id::kWhitePointChromaticityX, + &MasteringMetadata::white_point_chromaticity_x), + MakeChild<FloatParser>( + Id::kWhitePointChromaticityY, + &MasteringMetadata::white_point_chromaticity_y), + MakeChild<FloatParser>(Id::kLuminanceMax, + &MasteringMetadata::luminance_max), + MakeChild<FloatParser>(Id::kLuminanceMin, + &MasteringMetadata::luminance_min)) {} +}; + +} // namespace webm + +#endif // SRC_MASTERING_METADATA_PARSER_H_ diff --git a/webm_parser/src/parser.h b/webm_parser/src/parser.h new file mode 100644 index 0000000..4c449ed --- /dev/null +++ b/webm_parser/src/parser.h @@ -0,0 +1,34 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_PARSER_H_ +#define SRC_PARSER_H_ + +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +class Parser { + public: + virtual ~Parser() = default; + + // Feeds data into the parser, with the number of bytes read from the reader + // returned in num_bytes_read. Returns Status::kOkCompleted when parsing is + // complete, or an appropriate error code if the data is malformed and cannot + // be parsed. Otherwise, the status of Reader::Read is returned if only a + // partial parse could be done because the reader couldn't immediately provide + // all the needed data. reader and num_bytes_read must not be null. Do not + // call again once the parse is complete. + virtual Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) = 0; +}; + +} // namespace webm + +#endif // SRC_PARSER_H_ diff --git a/webm_parser/src/parser_utils.cc b/webm_parser/src/parser_utils.cc new file mode 100644 index 0000000..8563872 --- /dev/null +++ b/webm_parser/src/parser_utils.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/parser_utils.h" + +#include <cassert> +#include <cstdint> + +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +Status ReadByte(Reader* reader, std::uint8_t* byte) { + assert(reader != nullptr); + assert(byte != nullptr); + + std::uint64_t num_bytes_read; + const Status status = reader->Read(1, byte, &num_bytes_read); + + if (!status.completed_ok()) { + assert(num_bytes_read == 0); + } else { + assert(num_bytes_read == 1); + } + + return status; +} + +} // namespace webm diff --git a/webm_parser/src/parser_utils.h b/webm_parser/src/parser_utils.h new file mode 100644 index 0000000..becb1c7 --- /dev/null +++ b/webm_parser/src/parser_utils.h @@ -0,0 +1,64 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_PARSER_UTILS_H_ +#define SRC_PARSER_UTILS_H_ + +#include <cassert> +#include <cstdint> +#include <type_traits> + +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Reads a single byte from the reader, and returns the status of the read. If +// the status is not Status::kOkCompleted, then no data was read. +Status ReadByte(Reader* reader, std::uint8_t* byte); + +// Accumulates bytes from the reader into the integer. The integer will be +// extracted as a big-endian integer and stored with the native +// host-endianness. num_bytes_remaining is the number of bytes to +template <typename T> +Status AccumulateIntegerBytes(int num_to_read, Reader* reader, T* integer, + std::uint64_t* num_actually_read) { + static_assert(std::is_integral<T>::value || std::is_enum<T>::value, + "T must be an integer or enum type"); + // Use unsigned integers for bitwise arithmetic because it's well-defined (as + // opposed to signed integers, where left shifting a negative integer is + // undefined, for example). + using UnsignedT = typename std::make_unsigned<T>::type; + + assert(reader != nullptr); + assert(integer != nullptr); + assert(num_actually_read != nullptr); + assert(num_to_read >= 0); + assert(static_cast<std::size_t>(num_to_read) <= sizeof(T)); + + *num_actually_read = 0; + + if (num_to_read < 0 || static_cast<std::size_t>(num_to_read) > sizeof(T)) { + return Status(Status::kInvalidElementSize); + } + + for (; num_to_read > 0; --num_to_read) { + std::uint8_t byte; + const Status status = ReadByte(reader, &byte); + if (!status.completed_ok()) { + return status; + } + ++*num_actually_read; + *integer = static_cast<T>((static_cast<UnsignedT>(*integer) << 8) | byte); + } + + return Status(Status::kOkCompleted); +} + +} // namespace webm + +#endif // SRC_PARSER_UTILS_H_ diff --git a/webm_parser/src/projection_parser.h b/webm_parser/src/projection_parser.h new file mode 100644 index 0000000..a851eb6 --- /dev/null +++ b/webm_parser/src/projection_parser.h @@ -0,0 +1,40 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_PROJECTION_PARSER_H_ +#define SRC_PROJECTION_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/float_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#projection-master-element +class ProjectionParser : public MasterValueParser<Projection> { + public: + ProjectionParser() + : MasterValueParser( + MakeChild<IntParser<ProjectionType>>(Id::kProjectionType, + &Projection::type), + MakeChild<BinaryParser>(Id::kProjectionPrivate, + &Projection::projection_private), + MakeChild<FloatParser>(Id::kProjectionPoseYaw, + &Projection::pose_yaw), + MakeChild<FloatParser>(Id::kProjectionPosePitch, + &Projection::pose_pitch), + MakeChild<FloatParser>(Id::kProjectionPoseRoll, + &Projection::pose_roll)) {} +}; + +} // namespace webm + +#endif // SRC_PROJECTION_PARSER_H_ diff --git a/webm_parser/src/recursive_parser.h b/webm_parser/src/recursive_parser.h new file mode 100644 index 0000000..1646f2f --- /dev/null +++ b/webm_parser/src/recursive_parser.h @@ -0,0 +1,93 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_RECURSIVE_PARSER_H_ +#define SRC_RECURSIVE_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <memory> +#include <utility> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Lazily instantiates a parser of type T, and uses that parser to handle all +// parsing operations. The parser is allocated when Init is called. This class +// is intended to be used with recursive elements, where a parser needs to +// recursively instantiate parsers of the same type. +template <typename T> +class RecursiveParser : public ElementParser { + public: + explicit RecursiveParser(std::size_t max_recursion_depth = 25) + : max_recursion_depth_(max_recursion_depth){}; + + RecursiveParser(RecursiveParser&&) = default; + RecursiveParser& operator=(RecursiveParser&&) = default; + + RecursiveParser(const RecursiveParser&) = delete; + RecursiveParser& operator=(const RecursiveParser&) = delete; + + Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) override { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (max_recursion_depth_ == 0) { + return Status(Status::kExceededRecursionDepthLimit); + } + + if (!impl_) { + impl_.reset(new T(max_recursion_depth_ - 1)); + } + + return impl_->Init(metadata, max_size); + } + + void InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) override { + assert(max_recursion_depth_ > 0); + if (!impl_) { + impl_.reset(new T(max_recursion_depth_ - 1)); + } + + impl_->InitAfterSeek(child_ancestory, child_metadata); + } + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + assert(impl_ != nullptr); + + return impl_->Feed(callback, reader, num_bytes_read); + } + + decltype(std::declval<T>().value()) value() const { + assert(impl_ != nullptr); + + return impl_->value(); + } + + decltype(std::declval<T>().mutable_value()) mutable_value() { + assert(impl_ != nullptr); + + return impl_->mutable_value(); + } + + private: + std::unique_ptr<T> impl_; + std::size_t max_recursion_depth_; +}; + +} // namespace webm + +#endif // SRC_RECURSIVE_PARSER_H_ diff --git a/webm_parser/src/seek_head_parser.h b/webm_parser/src/seek_head_parser.h new file mode 100644 index 0000000..09e3727 --- /dev/null +++ b/webm_parser/src/seek_head_parser.h @@ -0,0 +1,27 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SEEK_HEAD_PARSER_H_ +#define SRC_SEEK_HEAD_PARSER_H_ + +#include "src/master_parser.h" +#include "src/seek_parser.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#SeekHead +// http://www.webmproject.org/docs/container/#SeekHead +class SeekHeadParser : public MasterParser { + public: + SeekHeadParser() : MasterParser(MakeChild<SeekParser>(Id::kSeek)) {} +}; + +} // namespace webm + +#endif // SRC_SEEK_HEAD_PARSER_H_ diff --git a/webm_parser/src/seek_parser.h b/webm_parser/src/seek_parser.h new file mode 100644 index 0000000..d2cc415 --- /dev/null +++ b/webm_parser/src/seek_parser.h @@ -0,0 +1,37 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SEEK_PARSER_H_ +#define SRC_SEEK_PARSER_H_ + +#include "src/id_element_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Seek +// http://www.webmproject.org/docs/container/#Seek +class SeekParser : public MasterValueParser<Seek> { + public: + SeekParser() + : MasterValueParser<Seek>( + MakeChild<IdElementParser>(Id::kSeekId, &Seek::id), + MakeChild<UnsignedIntParser>(Id::kSeekPosition, &Seek::position)) {} + + protected: + Status OnParseCompleted(Callback* callback) override { + return callback->OnSeek(metadata(Id::kSeek), value()); + } +}; + +} // namespace webm + +#endif // SRC_SEEK_PARSER_H_ diff --git a/webm_parser/src/segment_parser.cc b/webm_parser/src/segment_parser.cc new file mode 100644 index 0000000..9b2e7d5 --- /dev/null +++ b/webm_parser/src/segment_parser.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/segment_parser.h" + +#include "src/chapters_parser.h" +#include "src/cluster_parser.h" +#include "src/cues_parser.h" +#include "src/info_parser.h" +#include "src/seek_head_parser.h" +#include "src/skip_callback.h" +#include "src/tags_parser.h" +#include "src/tracks_parser.h" +#include "webm/id.h" + +namespace webm { + +SegmentParser::SegmentParser() + : MasterParser(MakeChild<ChaptersParser>(Id::kChapters), + MakeChild<ClusterParser>(Id::kCluster), + MakeChild<CuesParser>(Id::kCues), + MakeChild<InfoParser>(Id::kInfo), + MakeChild<SeekHeadParser>(Id::kSeekHead), + MakeChild<TagsParser>(Id::kTags), + MakeChild<TracksParser>(Id::kTracks)) {} + +Status SegmentParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + begin_done_ = false; + parse_completed_ = false; + return MasterParser::Init(metadata, max_size); +} + +void SegmentParser::InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) { + MasterParser::InitAfterSeek(child_ancestory, child_metadata); + + begin_done_ = true; + parse_completed_ = false; + action_ = Action::kRead; +} + +Status SegmentParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + if (!begin_done_) { + const ElementMetadata metadata{Id::kSegment, header_size(), size(), + position()}; + const Status status = callback->OnSegmentBegin(metadata, &action_); + if (!status.completed_ok()) { + return status; + } + begin_done_ = true; + } + + SkipCallback skip_callback; + if (action_ == Action::kSkip) { + callback = &skip_callback; + } + + if (!parse_completed_) { + const Status status = MasterParser::Feed(callback, reader, num_bytes_read); + if (!status.completed_ok()) { + return status; + } + parse_completed_ = true; + } + + return callback->OnSegmentEnd( + {Id::kSegment, header_size(), size(), position()}); +} + +bool SegmentParser::WasSkipped() const { return action_ == Action::kSkip; } + +} // namespace webm diff --git a/webm_parser/src/segment_parser.h b/webm_parser/src/segment_parser.h new file mode 100644 index 0000000..b5bb702 --- /dev/null +++ b/webm_parser/src/segment_parser.h @@ -0,0 +1,53 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SEGMENT_PARSER_H_ +#define SRC_SEGMENT_PARSER_H_ + +#include <cstdint> + +#include "src/master_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses Segment elements from a WebM byte stream. This class adheres to the +// ElementParser interface; see element_parser.h for further documentation on +// how it should be used. +// Spec reference: +// http://matroska.org/technical/specs/index.html#Segment +// http://www.webmproject.org/docs/container/#Segment +class SegmentParser : public MasterParser { + public: + SegmentParser(); + + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + void InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + bool WasSkipped() const override; + + private: + // Set to true iff Callback::OnSegmentBegin has completed. + bool begin_done_; + + // Set to true iff the base class has completed parsing. + bool parse_completed_; + + // The action requested by Callback::OnSegmentBegin. + Action action_ = Action::kRead; +}; + +} // namespace webm + +#endif // SRC_SEGMENT_PARSER_H_ diff --git a/webm_parser/src/simple_tag_parser.h b/webm_parser/src/simple_tag_parser.h new file mode 100644 index 0000000..d8b9da6 --- /dev/null +++ b/webm_parser/src/simple_tag_parser.h @@ -0,0 +1,37 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SIMPLE_TAG_PARSER_H_ +#define SRC_SIMPLE_TAG_PARSER_H_ + +#include "src/bool_parser.h" +#include "src/byte_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#SimpleTag +// http://www.webmproject.org/docs/container/#SimpleTag +class SimpleTagParser : public MasterValueParser<SimpleTag> { + public: + SimpleTagParser(std::size_t max_recursive_depth = 25) + : MasterValueParser<SimpleTag>( + MakeChild<StringParser>(Id::kTagName, &SimpleTag::name), + MakeChild<StringParser>(Id::kTagLanguage, &SimpleTag::language), + MakeChild<BoolParser>(Id::kTagDefault, &SimpleTag::is_default), + MakeChild<StringParser>(Id::kTagString, &SimpleTag::string), + MakeChild<BinaryParser>(Id::kTagBinary, &SimpleTag::binary), + MakeChild<SimpleTagParser>(Id::kSimpleTag, &SimpleTag::tags, + max_recursive_depth)) {} +}; + +} // namespace webm + +#endif // SRC_SIMPLE_TAG_PARSER_H_ diff --git a/webm_parser/src/size_parser.cc b/webm_parser/src/size_parser.cc new file mode 100644 index 0000000..3294382 --- /dev/null +++ b/webm_parser/src/size_parser.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/size_parser.h" + +#include <cassert> +#include <cstdint> +#include <limits> + +#include "src/bit_utils.h" +#include "src/parser_utils.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Spec references: +// http://matroska.org/technical/specs/index.html#EBML_ex +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#element-data-size +Status SizeParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + // Within the EBML header, the size can be encoded with 1-4 octets. After + // the EBML header, the size can be encoded with 1-8 octets (though not more + // than EBMLMaxSizeLength). + + Status status = uint_parser_.Feed(callback, reader, num_bytes_read); + + if (status.code == Status::kInvalidElementValue) { + status.code = Status::kInvalidElementSize; + } + + return status; +} + +std::uint64_t SizeParser::size() const { + // If all data bits are set, then it represents an unknown element size. + const std::uint64_t data_bits = + std::numeric_limits<std::uint64_t>::max() >> + (57 - 7 * (uint_parser_.encoded_length() - 1)); + if (uint_parser_.value() == data_bits) { + return kUnknownElementSize; + } + + return uint_parser_.value(); +} + +} // namespace webm diff --git a/webm_parser/src/size_parser.h b/webm_parser/src/size_parser.h new file mode 100644 index 0000000..d277f33 --- /dev/null +++ b/webm_parser/src/size_parser.h @@ -0,0 +1,43 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SIZE_PARSER_H_ +#define SRC_SIZE_PARSER_H_ + +#include <cstdint> + +#include "src/parser.h" +#include "src/var_int_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +class SizeParser : public Parser { + public: + SizeParser() = default; + SizeParser(SizeParser&&) = default; + SizeParser& operator=(SizeParser&&) = default; + + SizeParser(const SizeParser&) = delete; + SizeParser& operator=(const SizeParser&) = delete; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed size. This must not be called until the parse had been + // successfully completed. + std::uint64_t size() const; + + private: + VarIntParser uint_parser_; +}; + +} // namespace webm + +#endif // SRC_SIZE_PARSER_H_ diff --git a/webm_parser/src/skip_callback.h b/webm_parser/src/skip_callback.h new file mode 100644 index 0000000..e1c28e5 --- /dev/null +++ b/webm_parser/src/skip_callback.h @@ -0,0 +1,63 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SKIP_CALLBACK_H_ +#define SRC_SKIP_CALLBACK_H_ + +#include "webm/callback.h" +#include "webm/dom_types.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// An implementation of Callback that skips all elements. Every method that +// yields an action will yield Action::kSkip, and Reader::Skip will be called +// if the callback ever needs to process data from the byte stream. +class SkipCallback : public Callback { + public: + Status OnElementBegin(const ElementMetadata& /* metadata */, + Action* action) override { + *action = Action::kSkip; + return Status(Status::kOkCompleted); + } + + Status OnSegmentBegin(const ElementMetadata& /* metadata */, + Action* action) override { + *action = Action::kSkip; + return Status(Status::kOkCompleted); + } + + Status OnClusterBegin(const ElementMetadata& /* metadata */, + const Cluster& /* cluster */, Action* action) override { + *action = Action::kSkip; + return Status(Status::kOkCompleted); + } + + Status OnSimpleBlockBegin(const ElementMetadata& /* metadata */, + const SimpleBlock& /* simple_block */, + Action* action) override { + *action = Action::kSkip; + return Status(Status::kOkCompleted); + } + + Status OnBlockGroupBegin(const ElementMetadata& /* metadata */, + Action* action) override { + *action = Action::kSkip; + return Status(Status::kOkCompleted); + } + + Status OnBlockBegin(const ElementMetadata& /* metadata */, + const Block& /* block */, Action* action) override { + *action = Action::kSkip; + return Status(Status::kOkCompleted); + } +}; + +} // namespace webm + +#endif // SRC_SKIP_CALLBACK_H_ diff --git a/webm_parser/src/skip_parser.cc b/webm_parser/src/skip_parser.cc new file mode 100644 index 0000000..8f63f2b --- /dev/null +++ b/webm_parser/src/skip_parser.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/skip_parser.h" + +#include <cassert> +#include <cstdint> + +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +Status SkipParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == kUnknownElementSize) { + return Status(Status::kInvalidElementSize); + } + + num_bytes_remaining_ = metadata.size; + + return Status(Status::kOkCompleted); +} + +Status SkipParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + if (num_bytes_remaining_ == 0) { + return Status(Status::kOkCompleted); + } + + Status status; + do { + std::uint64_t local_num_bytes_read = 0; + status = reader->Skip(num_bytes_remaining_, &local_num_bytes_read); + assert((status.completed_ok() && + local_num_bytes_read == num_bytes_remaining_) || + (status.ok() && local_num_bytes_read < num_bytes_remaining_) || + (!status.ok() && local_num_bytes_read == 0)); + *num_bytes_read += local_num_bytes_read; + num_bytes_remaining_ -= local_num_bytes_read; + } while (status.code == Status::kOkPartial); + + return status; +} + +} // namespace webm diff --git a/webm_parser/src/skip_parser.h b/webm_parser/src/skip_parser.h new file mode 100644 index 0000000..6251bae --- /dev/null +++ b/webm_parser/src/skip_parser.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SKIP_PARSER_H_ +#define SRC_SKIP_PARSER_H_ + +#include <cstdint> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// A simple parser that merely skips (via Reader::Skip) ahead in the stream +// until the element has been fully skipped. +class SkipParser : public ElementParser { + public: + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + private: + std::uint64_t num_bytes_remaining_; +}; + +} // namespace webm + +#endif // SRC_SKIP_PARSER_H_ diff --git a/webm_parser/src/slices_parser.h b/webm_parser/src/slices_parser.h new file mode 100644 index 0000000..ae19c86 --- /dev/null +++ b/webm_parser/src/slices_parser.h @@ -0,0 +1,30 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_SLICES_PARSER_H_ +#define SRC_SLICES_PARSER_H_ + +#include "src/master_value_parser.h" +#include "src/time_slice_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Slices +// http://www.webmproject.org/docs/container/#Slices +class SlicesParser : public MasterValueParser<Slices> { + public: + SlicesParser() + : MasterValueParser<Slices>( + MakeChild<TimeSliceParser>(Id::kTimeSlice, &Slices::slices)) {} +}; + +} // namespace webm + +#endif // SRC_SLICES_PARSER_H_ diff --git a/webm_parser/src/tag_parser.h b/webm_parser/src/tag_parser.h new file mode 100644 index 0000000..9cf8cf9 --- /dev/null +++ b/webm_parser/src/tag_parser.h @@ -0,0 +1,37 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_TAG_PARSER_H_ +#define SRC_TAG_PARSER_H_ + +#include "src/master_value_parser.h" +#include "src/simple_tag_parser.h" +#include "src/targets_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Tag +// http://www.webmproject.org/docs/container/#Tag +class TagParser : public MasterValueParser<Tag> { + public: + TagParser() + : MasterValueParser<Tag>( + MakeChild<TargetsParser>(Id::kTargets, &Tag::targets), + MakeChild<SimpleTagParser>(Id::kSimpleTag, &Tag::tags)) {} + + protected: + Status OnParseCompleted(Callback* callback) override { + return callback->OnTag(metadata(Id::kTag), value()); + } +}; + +} // namespace webm + +#endif // SRC_TAG_PARSER_H_ diff --git a/webm_parser/src/tags_parser.h b/webm_parser/src/tags_parser.h new file mode 100644 index 0000000..4784235 --- /dev/null +++ b/webm_parser/src/tags_parser.h @@ -0,0 +1,27 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_TAGS_PARSER_H_ +#define SRC_TAGS_PARSER_H_ + +#include "src/master_parser.h" +#include "src/tag_parser.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Tags +// http://www.webmproject.org/docs/container/#Tags +class TagsParser : public MasterParser { + public: + TagsParser() : MasterParser(MakeChild<TagParser>(Id::kTag)) {} +}; + +} // namespace webm + +#endif // SRC_TAGS_PARSER_H_ diff --git a/webm_parser/src/targets_parser.h b/webm_parser/src/targets_parser.h new file mode 100644 index 0000000..043c110 --- /dev/null +++ b/webm_parser/src/targets_parser.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_TARGETS_PARSER_H_ +#define SRC_TARGETS_PARSER_H_ + +#include "src/byte_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Targets +// http://www.webmproject.org/docs/container/#Targets +class TargetsParser : public MasterValueParser<Targets> { + public: + TargetsParser() + : MasterValueParser<Targets>( + MakeChild<UnsignedIntParser>(Id::kTargetTypeValue, + &Targets::type_value), + MakeChild<StringParser>(Id::kTargetType, &Targets::type), + MakeChild<UnsignedIntParser>(Id::kTagTrackUid, + &Targets::track_uids)) {} +}; + +} // namespace webm + +#endif // SRC_TARGETS_PARSER_H_ diff --git a/webm_parser/src/time_slice_parser.h b/webm_parser/src/time_slice_parser.h new file mode 100644 index 0000000..a8fa41c --- /dev/null +++ b/webm_parser/src/time_slice_parser.h @@ -0,0 +1,30 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_TIME_SLICE_PARSER_H_ +#define SRC_TIME_SLICE_PARSER_H_ + +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#TimeSlice +// http://www.webmproject.org/docs/container/#TimeSlice +class TimeSliceParser : public MasterValueParser<TimeSlice> { + public: + TimeSliceParser() + : MasterValueParser<TimeSlice>(MakeChild<UnsignedIntParser>( + Id::kLaceNumber, &TimeSlice::lace_number)) {} +}; + +} // namespace webm + +#endif // SRC_TIME_SLICE_PARSER_H_ diff --git a/webm_parser/src/track_entry_parser.h b/webm_parser/src/track_entry_parser.h new file mode 100644 index 0000000..d103864 --- /dev/null +++ b/webm_parser/src/track_entry_parser.h @@ -0,0 +1,65 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef SRC_TRACK_ENTRY_PARSER_H_ +#define SRC_TRACK_ENTRY_PARSER_H_ + +#include "src/audio_parser.h" +#include "src/bool_parser.h" +#include "src/byte_parser.h" +#include "src/content_encodings_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "src/video_parser.h" +#include "webm/dom_types.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#TrackEntry +// http://www.webmproject.org/docs/container/#TrackEntry +class TrackEntryParser : public MasterValueParser<TrackEntry> { + public: + TrackEntryParser() + : MasterValueParser<TrackEntry>( + MakeChild<UnsignedIntParser>(Id::kTrackNumber, + &TrackEntry::track_number), + MakeChild<UnsignedIntParser>(Id::kTrackUid, &TrackEntry::track_uid), + MakeChild<IntParser<TrackType>>(Id::kTrackType, + &TrackEntry::track_type), + MakeChild<BoolParser>(Id::kFlagEnabled, &TrackEntry::is_enabled), + MakeChild<BoolParser>(Id::kFlagDefault, &TrackEntry::is_default), + MakeChild<BoolParser>(Id::kFlagForced, &TrackEntry::is_forced), + MakeChild<BoolParser>(Id::kFlagLacing, &TrackEntry::uses_lacing), + MakeChild<UnsignedIntParser>(Id::kDefaultDuration, + &TrackEntry::default_duration), + MakeChild<StringParser>(Id::kName, &TrackEntry::name), + MakeChild<StringParser>(Id::kLanguage, &TrackEntry::language), + MakeChild<StringParser>(Id::kCodecId, &TrackEntry::codec_id), + MakeChild<BinaryParser>(Id::kCodecPrivate, + &TrackEntry::codec_private), + MakeChild<StringParser>(Id::kCodecName, &TrackEntry::codec_name), + MakeChild<UnsignedIntParser>(Id::kCodecDelay, + &TrackEntry::codec_delay), + MakeChild<UnsignedIntParser>(Id::kSeekPreRoll, + &TrackEntry::seek_pre_roll), + MakeChild<VideoParser>(Id::kVideo, &TrackEntry::video), + MakeChild<AudioParser>(Id::kAudio, &TrackEntry::audio), + MakeChild<ContentEncodingsParser>( + Id::kContentEncodings, &TrackEntry::content_encodings)) {} + + protected: + Status OnParseCompleted(Callback* callback) override { + return callback->OnTrackEntry(metadata(Id::kTrackEntry), value()); + } +}; + +} // namespace webm + +#endif // SRC_TRACK_ENTRY_PARSER_H_ diff --git a/webm_parser/src/tracks_parser.h b/webm_parser/src/tracks_parser.h new file mode 100644 index 0000000..57d25c4 --- /dev/null +++ b/webm_parser/src/tracks_parser.h @@ -0,0 +1,27 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_TRACKS_PARSER_H_ +#define SRC_TRACKS_PARSER_H_ + +#include "src/master_parser.h" +#include "src/track_entry_parser.h" +#include "webm/id.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Tracks +// http://www.webmproject.org/docs/container/#Tracks +class TracksParser : public MasterParser { + public: + TracksParser() : MasterParser(MakeChild<TrackEntryParser>(Id::kTrackEntry)) {} +}; + +} // namespace webm + +#endif // SRC_TRACKS_PARSER_H_ diff --git a/webm_parser/src/unknown_parser.cc b/webm_parser/src/unknown_parser.cc new file mode 100644 index 0000000..042cf71 --- /dev/null +++ b/webm_parser/src/unknown_parser.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/unknown_parser.h" + +#include <cassert> +#include <cstdint> + +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +Status UnknownParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == kUnknownElementSize) { + return Status(Status::kIndefiniteUnknownElement); + } + + metadata_ = metadata; + bytes_remaining_ = metadata.size; + + return Status(Status::kOkCompleted); +} + +Status UnknownParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + const std::uint64_t original_bytes_remaining = bytes_remaining_; + const Status status = + callback->OnUnknownElement(metadata_, reader, &bytes_remaining_); + assert(bytes_remaining_ <= original_bytes_remaining); + + *num_bytes_read = original_bytes_remaining - bytes_remaining_; + + return status; +} + +} // namespace webm diff --git a/webm_parser/src/unknown_parser.h b/webm_parser/src/unknown_parser.h new file mode 100644 index 0000000..d625bab --- /dev/null +++ b/webm_parser/src/unknown_parser.h @@ -0,0 +1,38 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_UNKNOWN_PARSER_H_ +#define SRC_UNKNOWN_PARSER_H_ + +#include <cstdint> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses unknown elements by delegating to Callback::OnUnknownElement. +class UnknownParser : public ElementParser { + public: + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + private: + // The metadata for this element. + ElementMetadata metadata_; + + // The number of bytes remaining that have not been read in the element. + std::uint64_t bytes_remaining_; +}; + +} // namespace webm + +#endif // SRC_UNKNOWN_PARSER_H_ diff --git a/webm_parser/src/var_int_parser.cc b/webm_parser/src/var_int_parser.cc new file mode 100644 index 0000000..5db9c44 --- /dev/null +++ b/webm_parser/src/var_int_parser.cc @@ -0,0 +1,71 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/var_int_parser.h" + +#include <cassert> +#include <cstdint> +#include <limits> + +#include "src/bit_utils.h" +#include "src/parser_utils.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Spec references: +// http://matroska.org/technical/specs/index.html#EBML_ex +// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#variable-size-integer +Status VarIntParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + assert(num_bytes_remaining_ != 0); + + *num_bytes_read = 0; + + if (num_bytes_remaining_ == -1) { + std::uint8_t first_byte; + const Status status = ReadByte(reader, &first_byte); + if (!status.completed_ok()) { + return status; + } + ++*num_bytes_read; + + // The first byte must have a marker bit set to indicate how many octets are + // used. + if (first_byte == 0) { + return Status(Status::kInvalidElementValue); + } + + total_data_bytes_ = CountLeadingZeros(first_byte); + num_bytes_remaining_ = total_data_bytes_; + + value_ = first_byte; + } + + std::uint64_t local_num_bytes_read; + const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader, + &value_, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + num_bytes_remaining_ -= static_cast<int>(local_num_bytes_read); + + if (!status.completed_ok()) { + return status; + } + + // Clear the marker bit. + constexpr std::uint64_t all_bits = std::numeric_limits<std::uint64_t>::max(); + const std::uint64_t data_bits = all_bits >> (57 - 7 * total_data_bytes_); + value_ &= data_bits; + + return Status(Status::kOkCompleted); +} + +} // namespace webm diff --git a/webm_parser/src/var_int_parser.h b/webm_parser/src/var_int_parser.h new file mode 100644 index 0000000..2ec51e3 --- /dev/null +++ b/webm_parser/src/var_int_parser.h @@ -0,0 +1,56 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_VAR_INT_PARSER_H_ +#define SRC_VAR_INT_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +class VarIntParser : public Parser { + public: + VarIntParser() = default; + VarIntParser(VarIntParser&&) = default; + VarIntParser& operator=(VarIntParser&&) = default; + + VarIntParser(const VarIntParser&) = delete; + VarIntParser& operator=(const VarIntParser&) = delete; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed value. This must not be called until the parse had been + // successfully completed. + std::uint64_t value() const { + assert(num_bytes_remaining_ == 0); + return value_; + } + + // Gets the number of bytes which were used to encode the integer value in the + // byte stream. This must not be called until the parse had been successfully + // completed. + int encoded_length() const { + assert(num_bytes_remaining_ == 0); + return total_data_bytes_ + 1; + } + + private: + int num_bytes_remaining_ = -1; + int total_data_bytes_; + std::uint64_t value_; +}; + +} // namespace webm + +#endif // SRC_VAR_INT_PARSER_H_ diff --git a/webm_parser/src/video_parser.h b/webm_parser/src/video_parser.h new file mode 100644 index 0000000..20c12e2 --- /dev/null +++ b/webm_parser/src/video_parser.h @@ -0,0 +1,122 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_VIDEO_PARSER_H_ +#define SRC_VIDEO_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/bool_parser.h" +#include "src/colour_parser.h" +#include "src/float_parser.h" +#include "src/int_parser.h" +#include "src/master_value_parser.h" +#include "src/projection_parser.h" +#include "webm/callback.h" +#include "webm/dom_types.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#Video +// http://www.webmproject.org/docs/container/#Video +class VideoParser : public MasterValueParser<Video> { + public: + VideoParser() + : MasterValueParser<Video>( + MakeChild<IntParser<FlagInterlaced>>(Id::kFlagInterlaced, + &Video::interlaced), + MakeChild<IntParser<StereoMode>>(Id::kStereoMode, + &Video::stereo_mode), + MakeChild<UnsignedIntParser>(Id::kAlphaMode, &Video::alpha_mode), + MakeChild<UnsignedIntParser>(Id::kPixelWidth, &Video::pixel_width), + MakeChild<UnsignedIntParser>(Id::kPixelHeight, + &Video::pixel_height), + MakeChild<UnsignedIntParser>(Id::kPixelCropBottom, + &Video::pixel_crop_bottom), + MakeChild<UnsignedIntParser>(Id::kPixelCropTop, + &Video::pixel_crop_top), + MakeChild<UnsignedIntParser>(Id::kPixelCropLeft, + &Video::pixel_crop_left), + MakeChild<UnsignedIntParser>(Id::kPixelCropRight, + &Video::pixel_crop_right), + MakeChild<UnsignedIntParser>(Id::kDisplayWidth, + &Video::display_width) + .NotifyOnParseComplete(), + MakeChild<UnsignedIntParser>(Id::kDisplayHeight, + &Video::display_height) + .NotifyOnParseComplete(), + MakeChild<IntParser<DisplayUnit>>(Id::kDisplayUnit, + &Video::display_unit), + MakeChild<IntParser<AspectRatioType>>(Id::kAspectRatioType, + &Video::aspect_ratio_type), + MakeChild<FloatParser>(Id::kFrameRate, &Video::frame_rate), + MakeChild<ColourParser>(Id::kColour, &Video::colour), + MakeChild<ProjectionParser>(Id::kProjection, &Video::projection)) {} + + Status Init(const ElementMetadata& metadata, + std::uint64_t max_size) override { + display_width_has_value_ = false; + display_height_has_value_ = false; + + return MasterValueParser::Init(metadata, max_size); + } + + void InitAfterSeek(const Ancestory& child_ancestory, + const ElementMetadata& child_metadata) override { + display_width_has_value_ = false; + display_height_has_value_ = false; + + return MasterValueParser::InitAfterSeek(child_ancestory, child_metadata); + } + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override { + const Status status = + MasterValueParser::Feed(callback, reader, num_bytes_read); + if (status.completed_ok()) { + FixMissingDisplaySize(); + } + return status; + } + + protected: + void OnChildParsed(const ElementMetadata& metadata) override { + assert(metadata.id == Id::kDisplayWidth || + metadata.id == Id::kDisplayHeight); + + if (metadata.id == Id::kDisplayWidth) { + display_width_has_value_ = metadata.size > 0; + } else { + display_height_has_value_ = metadata.size > 0; + } + } + + private: + bool display_width_has_value_; + bool display_height_has_value_; + + void FixMissingDisplaySize() { + if (!display_width_has_value_) { + *mutable_value()->display_width.mutable_value() = + value().pixel_width.value(); + } + + if (!display_height_has_value_) { + *mutable_value()->display_height.mutable_value() = + value().pixel_height.value(); + } + } +}; + +} // namespace webm + +#endif // SRC_VIDEO_PARSER_H_ diff --git a/webm_parser/src/virtual_block_parser.cc b/webm_parser/src/virtual_block_parser.cc new file mode 100644 index 0000000..712c77f --- /dev/null +++ b/webm_parser/src/virtual_block_parser.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/virtual_block_parser.h" + +#include <cassert> +#include <cstdint> + +#include "webm/element.h" + +namespace webm { + +Status VirtualBlockParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == kUnknownElementSize || metadata.size < 4) { + return Status(Status::kInvalidElementSize); + } + + *this = {}; + my_size_ = metadata.size; + + return Status(Status::kOkCompleted); +} + +Status VirtualBlockParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + *num_bytes_read = 0; + + Status status; + std::uint64_t local_num_bytes_read; + + while (true) { + switch (state_) { + case State::kReadingHeader: { + status = parser_.Feed(callback, reader, &local_num_bytes_read); + *num_bytes_read += local_num_bytes_read; + total_bytes_read_ += local_num_bytes_read; + if (!status.completed_ok()) { + return status; + } + value_.track_number = parser_.value().track_number; + value_.timecode = parser_.value().timecode; + state_ = State::kValidatingSize; + continue; + } + + case State::kValidatingSize: { + if (my_size_ < total_bytes_read_) { + return Status(Status::kInvalidElementValue); + } + state_ = State::kDone; + continue; + } + + case State::kDone: { + return Status(Status::kOkCompleted); + } + } + } +} + +} // namespace webm diff --git a/webm_parser/src/virtual_block_parser.h b/webm_parser/src/virtual_block_parser.h new file mode 100644 index 0000000..d7b8cd7 --- /dev/null +++ b/webm_parser/src/virtual_block_parser.h @@ -0,0 +1,69 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_VIRTUAL_BLOCK_PARSER_H_ +#define SRC_VIRTUAL_BLOCK_PARSER_H_ + +#include <cassert> +#include <cstdint> + +#include "src/block_header_parser.h" +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/dom_types.h" +#include "webm/element.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Spec reference: +// http://matroska.org/technical/specs/index.html#BlockVirtual +// http://www.webmproject.org/docs/container/#BlockVirtual +// http://matroska.org/technical/specs/index.html#block_virtual +class VirtualBlockParser : public ElementParser { + public: + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + // Gets the parsed block header information. This must not be called until the + // parse has been successfully completed. + const VirtualBlock& value() const { + assert(state_ == State::kDone); + return value_; + } + + // Gets the parsed block header information. This must not be called until the + // parse has been successfully completed. + VirtualBlock* mutable_value() { + assert(state_ == State::kDone); + return &value_; + } + + private: + std::uint64_t my_size_; + std::uint64_t total_bytes_read_ = 0; + + VirtualBlock value_{}; + + BlockHeaderParser parser_; + + enum class State { + /* clang-format off */ + // State Transitions to state When + kReadingHeader, // kValidatingSize header parsed + kValidatingSize, // kDone no errors + kDone, // No transitions from here (must call Init) + /* clang-format on */ + } state_ = State::kReadingHeader; +}; + +} // namespace webm + +#endif // SRC_VIRTUAL_BLOCK_PARSER_H_ diff --git a/webm_parser/src/void_parser.cc b/webm_parser/src/void_parser.cc new file mode 100644 index 0000000..043bced --- /dev/null +++ b/webm_parser/src/void_parser.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/void_parser.h" + +#include <cassert> +#include <cstdint> + +#include "webm/callback.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +Status VoidParser::Init(const ElementMetadata& metadata, + std::uint64_t max_size) { + assert(metadata.size == kUnknownElementSize || metadata.size <= max_size); + + if (metadata.size == kUnknownElementSize) { + return Status(Status::kInvalidElementSize); + } + + metadata_ = metadata; + bytes_remaining_ = metadata.size; + + return Status(Status::kOkCompleted); +} + +Status VoidParser::Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) { + assert(callback != nullptr); + assert(reader != nullptr); + assert(num_bytes_read != nullptr); + + const std::uint64_t original_bytes_remaining = bytes_remaining_; + const Status status = callback->OnVoid(metadata_, reader, &bytes_remaining_); + assert(bytes_remaining_ <= original_bytes_remaining); + + *num_bytes_read = original_bytes_remaining - bytes_remaining_; + return status; +} + +} // namespace webm diff --git a/webm_parser/src/void_parser.h b/webm_parser/src/void_parser.h new file mode 100644 index 0000000..e793faf --- /dev/null +++ b/webm_parser/src/void_parser.h @@ -0,0 +1,41 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef SRC_VOID_PARSER_H_ +#define SRC_VOID_PARSER_H_ + +#include <cstdint> + +#include "src/element_parser.h" +#include "webm/callback.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Parses a Void element by delegating to Callback::OnVoid. +// Spec reference: +// http://matroska.org/technical/specs/index.html#Void +// http://www.webmproject.org/docs/container/#Void +class VoidParser : public ElementParser { + public: + Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override; + + Status Feed(Callback* callback, Reader* reader, + std::uint64_t* num_bytes_read) override; + + private: + // The metadata for this element. + ElementMetadata metadata_; + + // The number of bytes remaining that have not been read in the element. + std::uint64_t bytes_remaining_ = 0; +}; + +} // namespace webm + +#endif // SRC_VOID_PARSER_H_ diff --git a/webm_parser/src/webm_parser.cc b/webm_parser/src/webm_parser.cc new file mode 100644 index 0000000..cf8bc94 --- /dev/null +++ b/webm_parser/src/webm_parser.cc @@ -0,0 +1,282 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/webm_parser.h" + +#include <cassert> +#include <cstdint> + +#include "src/ebml_parser.h" +#include "src/master_parser.h" +#include "src/segment_parser.h" +#include "src/unknown_parser.h" +#include "webm/element.h" + +namespace webm { + +// Parses WebM EBML documents (i.e. level-0 WebM elements). +class WebmParser::DocumentParser { + public: + // Resets the parser after a seek to a new position in the reader. + void DidSeek() { + PrepareForNextChild(); + did_seek_ = true; + state_ = State::kBegin; + } + + // Feeds the parser; will return Status::kOkCompleted when the reader returns + // Status::kEndOfFile, but only if the parser has already completed parsing + // its child elements. + Status Feed(Callback* callback, Reader* reader) { + assert(callback != nullptr); + assert(reader != nullptr); + + Callback* const original_callback = callback; + if (action_ == Action::kSkip) { + callback = &skip_callback_; + } + + Status status; + std::uint64_t num_bytes_read; + while (true) { + switch (state_) { + case State::kBegin: { + child_metadata_.header_size = 0; + child_metadata_.position = reader->Position(); + state_ = State::kReadingChildId; + continue; + } + + case State::kReadingChildId: { + assert(child_parser_ == nullptr); + status = id_parser_.Feed(callback, reader, &num_bytes_read); + child_metadata_.header_size += num_bytes_read; + if (!status.completed_ok()) { + if (status.code == Status::kEndOfFile && + reader->Position() == child_metadata_.position) { + state_ = State::kEndReached; + continue; + } + return status; + } + state_ = State::kReadingChildSize; + continue; + } + + case State::kReadingChildSize: { + assert(child_parser_ == nullptr); + status = size_parser_.Feed(callback, reader, &num_bytes_read); + child_metadata_.header_size += num_bytes_read; + if (!status.completed_ok()) { + return status; + } + child_metadata_.id = id_parser_.id(); + child_metadata_.size = size_parser_.size(); + state_ = State::kValidatingChildSize; + continue; + } + + case State::kValidatingChildSize: { + assert(child_parser_ == nullptr); + + if (child_metadata_.id == Id::kSegment) { + child_parser_ = &segment_parser_; + did_seek_ = false; + state_ = State::kGettingAction; + continue; + } else if (child_metadata_.id == Id::kEbml) { + child_parser_ = &ebml_parser_; + did_seek_ = false; + state_ = State::kGettingAction; + continue; + } + + Ancestory ancestory; + if (did_seek_ && Ancestory::ById(child_metadata_.id, &ancestory)) { + assert(!ancestory.empty()); + assert(ancestory.id() == Id::kSegment || + ancestory.id() == Id::kEbml); + + if (ancestory.id() == Id::kSegment) { + child_parser_ = &segment_parser_; + } else { + child_parser_ = &ebml_parser_; + } + + child_parser_->InitAfterSeek(ancestory.next(), child_metadata_); + child_metadata_.id = ancestory.id(); + child_metadata_.header_size = kUnknownHeaderSize; + child_metadata_.size = kUnknownElementSize; + child_metadata_.position = kUnknownElementPosition; + did_seek_ = false; + action_ = Action::kRead; + state_ = State::kReadingChildBody; + continue; + } + + if (child_metadata_.id == Id::kVoid) { + child_parser_ = &void_parser_; + } else { + if (child_metadata_.size == kUnknownElementSize) { + return Status(Status::kIndefiniteUnknownElement); + } + child_parser_ = &unknown_parser_; + } + state_ = State::kGettingAction; + continue; + } + + case State::kGettingAction: { + assert(child_parser_ != nullptr); + status = callback->OnElementBegin(child_metadata_, &action_); + if (!status.completed_ok()) { + return status; + } + + if (action_ == Action::kSkip) { + callback = &skip_callback_; + if (child_metadata_.size != kUnknownElementSize) { + child_parser_ = &skip_parser_; + } + } + state_ = State::kInitializingChildParser; + continue; + } + + case State::kInitializingChildParser: { + assert(child_parser_ != nullptr); + status = child_parser_->Init(child_metadata_, child_metadata_.size); + if (!status.completed_ok()) { + return status; + } + state_ = State::kReadingChildBody; + continue; + } + + case State::kReadingChildBody: { + assert(child_parser_ != nullptr); + status = child_parser_->Feed(callback, reader, &num_bytes_read); + if (!status.completed_ok()) { + return status; + } + if (child_parser_->GetCachedMetadata(&child_metadata_)) { + state_ = State::kValidatingChildSize; + } else { + child_metadata_.header_size = 0; + state_ = State::kReadingChildId; + } + PrepareForNextChild(); + callback = original_callback; + child_metadata_.position = reader->Position(); + continue; + } + + case State::kEndReached: { + return Status(Status::kOkCompleted); + } + } + } + } + + private: + // Parsing states for the finite-state machine. + enum class State { + /* clang-format off */ + // State Transitions to state When + kBegin, // kReadingChildId done + kReadingChildId, // kReadingChildSize done + // kEndReached EOF + kReadingChildSize, // kValidatingChildSize done + kValidatingChildSize, // kGettingAction done + kGettingAction, // kInitializingChildParser done + kInitializingChildParser, // kReadingChildBody done + kReadingChildBody, // kValidatingChildSize cached metadata + // kReadingChildId otherwise + kEndReached, // No transitions from here + /* clang-format on */ + }; + + // The parser for parsing child element Ids. + IdParser id_parser_; + + // The parser for parsing child element sizes. + SizeParser size_parser_; + + // The parser for Id::kEbml elements. + EbmlParser ebml_parser_; + + // The parser for Id::kSegment child elements. + SegmentParser segment_parser_; + + // The parser for Id::kVoid child elements. + VoidParser void_parser_; + + // The parser used when skipping elements (if the element's size is known). + SkipParser skip_parser_; + + // The parser used for unknown children. + UnknownParser unknown_parser_; + + // The callback used when skipping elements. + SkipCallback skip_callback_; + + // The parser that is parsing the current child element. + ElementParser* child_parser_ = nullptr; + + // Metadata for the current child being parsed. + ElementMetadata child_metadata_ = {}; + + // Action for the current child being parsed. + Action action_ = Action::kRead; + + // True if a seek was performed and the parser needs to handle it. + bool did_seek_ = false; + + // The current state of the finite state machine. + State state_ = State::kBegin; + + // Resets state in preparation for parsing a child element. + void PrepareForNextChild() { + id_parser_ = {}; + size_parser_ = {}; + child_parser_ = nullptr; + action_ = Action::kRead; + } +}; + +// We have to explicitly declare a destructor (even if it's just defaulted) +// because using the pimpl idiom with std::unique_ptr requires it. See Herb +// Sutter's GotW #100 for further explanation. +WebmParser::~WebmParser() = default; + +WebmParser::WebmParser() : parser_(new DocumentParser) {} + +void WebmParser::DidSeek() { + parser_->DidSeek(); + parsing_status_ = Status(Status::kOkPartial); +} + +Status WebmParser::Feed(Callback* callback, Reader* reader) { + assert(callback != nullptr); + assert(reader != nullptr); + + if (parsing_status_.is_parsing_error()) { + return parsing_status_; + } + parsing_status_ = parser_->Feed(callback, reader); + return parsing_status_; +} + +void WebmParser::Swap(WebmParser* other) { + assert(other != nullptr); + parser_.swap(other->parser_); + std::swap(parsing_status_, other->parsing_status_); +} + +void swap(WebmParser& left, WebmParser& right) { left.Swap(&right); } + +} // namespace webm diff --git a/webm_parser/test_utils/element_parser_test.h b/webm_parser/test_utils/element_parser_test.h new file mode 100644 index 0000000..d0a6530 --- /dev/null +++ b/webm_parser/test_utils/element_parser_test.h @@ -0,0 +1,114 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef TEST_UTILS_ELEMENT_PARSER_TEST_H_ +#define TEST_UTILS_ELEMENT_PARSER_TEST_H_ + +#include <cstdint> +#include <vector> + +#include "gtest/gtest.h" + +#include "test_utils/limited_reader.h" +#include "test_utils/parser_test.h" +#include "webm/buffer_reader.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Base class for unit tests that test an instance of the ElementParser +// inteface. The template parameter T is the parser class being tested, and the +// optional id is the element ID associated with elements from the parser. +template <typename T, Id id = static_cast<Id>(0)> +class ElementParserTest : public ParserTest<T> { + public: + // Sets the reader's internal buffer to the given buffer and metadata_ to + // data.size(). + void SetReaderData(std::vector<std::uint8_t> data) override { + metadata_.size = data.size(); + ParserTest<T>::SetReaderData(std::move(data)); + } + + // Sets metadata_.size to size and then calls Init() on the parser, ensuring + // that it returns the expected status code. + void TestInit(std::uint64_t size, Status::Code expected) { + metadata_.size = size; + const Status status = parser_.Init(metadata_, metadata_.size); + ASSERT_EQ(expected, status.code); + } + + // Similar to the base class implementation, but with the difference that + // Init() is also called (after setting metadata_.size to size). + void ParseAndVerify(std::uint64_t size) override { + TestInit(size, Status::kOkCompleted); + + std::uint64_t num_bytes_read = 0; + const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read); + ASSERT_EQ(Status::kOkCompleted, status.code); + + if (size != kUnknownElementSize) { + ASSERT_EQ(size, num_bytes_read); + } + } + + void ParseAndVerify() override { ParseAndVerify(metadata_.size); } + + void IncrementalParseAndVerify() override { + TestInit(metadata_.size, Status::kOkCompleted); + + webm::LimitedReader limited_reader( + std::unique_ptr<webm::Reader>(new BufferReader(std::move(reader_)))); + + Status status; + std::uint64_t num_bytes_read = 0; + do { + limited_reader.set_total_read_skip_limit(1); + std::uint64_t local_num_bytes_read = 0; + status = parser_.Feed(&callback_, &limited_reader, &local_num_bytes_read); + num_bytes_read += local_num_bytes_read; + ASSERT_GE(static_cast<std::uint64_t>(1), local_num_bytes_read); + } while (status.code == Status::kWouldBlock || + status.code == Status::kOkPartial); + + ASSERT_EQ(Status::kOkCompleted, status.code); + + if (metadata_.size != kUnknownElementSize) { + ASSERT_EQ(metadata_.size, num_bytes_read); + } + } + + // Initializes the parser (after setting metadata_.size to size), ensures it + // succeeds, and then calls Feed() on the parser, making sure it returns the + // expected status code. + void ParseAndExpectResult(Status::Code expected, std::uint64_t size) { + TestInit(size, Status::kOkCompleted); + + std::uint64_t num_bytes_read = 0; + const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read); + ASSERT_EQ(expected, status.code); + } + + // Initializes the parser, ensures it succeeds, and then calls Feed() on the + // parser, making sure it returns the expected status code. + void ParseAndExpectResult(Status::Code expected) override { + ParseAndExpectResult(expected, metadata_.size); + } + + protected: + using ParserTest<T>::callback_; + using ParserTest<T>::parser_; + using ParserTest<T>::reader_; + + // Element metadata associated with the element parsed by parser_. This is + // passed to Init() when initializing the parser. + ElementMetadata metadata_ = {id, 0, 0, 0}; +}; + +} // namespace webm + +#endif // TEST_UTILS_ELEMENT_PARSER_TEST_H_ diff --git a/webm_parser/test_utils/limited_reader.cc b/webm_parser/test_utils/limited_reader.cc new file mode 100644 index 0000000..f1228a1 --- /dev/null +++ b/webm_parser/test_utils/limited_reader.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "test_utils/limited_reader.h" + +namespace webm { + +LimitedReader::LimitedReader(std::unique_ptr<Reader> impl) + : impl_(std::move(impl)) {} + +Status LimitedReader::Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) { + assert(num_to_read > 0); + assert(buffer != nullptr); + assert(num_actually_read != nullptr); + + *num_actually_read = 0; + std::size_t expected = num_to_read; + + num_to_read = std::min({num_to_read, single_read_limit_, total_read_limit_}); + + // Handle total_read_skip_limit_ separately since std::size_t can be + // smaller than std::uint64_t. + if (num_to_read > total_read_skip_limit_) { + num_to_read = static_cast<std::size_t>(total_read_skip_limit_); + } + + if (num_to_read == 0) { + return return_status_when_blocked_; + } + + Status status = impl_->Read(num_to_read, buffer, num_actually_read); + assert(*num_actually_read <= num_to_read); + + if (status.code == Status::kOkCompleted && *num_actually_read < expected) { + status.code = Status::kOkPartial; + } + + if (total_read_limit_ != std::numeric_limits<std::size_t>::max()) { + total_read_limit_ -= static_cast<std::size_t>(*num_actually_read); + } + + if (total_read_skip_limit_ != std::numeric_limits<std::uint64_t>::max()) { + total_read_skip_limit_ -= *num_actually_read; + } + + return status; +} + +Status LimitedReader::Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) { + assert(num_to_skip > 0); + assert(num_actually_skipped != nullptr); + + *num_actually_skipped = 0; + std::uint64_t expected = num_to_skip; + + num_to_skip = std::min({num_to_skip, single_skip_limit_, total_skip_limit_, + total_read_skip_limit_}); + + if (num_to_skip == 0) { + return return_status_when_blocked_; + } + + Status status = impl_->Skip(num_to_skip, num_actually_skipped); + assert(*num_actually_skipped <= num_to_skip); + + if (status.code == Status::kOkCompleted && *num_actually_skipped < expected) { + status.code = Status::kOkPartial; + } + + if (total_skip_limit_ != std::numeric_limits<std::uint64_t>::max()) { + total_skip_limit_ -= *num_actually_skipped; + } + + if (total_read_skip_limit_ != std::numeric_limits<std::uint64_t>::max()) { + total_read_skip_limit_ -= *num_actually_skipped; + } + + return status; +} + +std::uint64_t LimitedReader::Position() const { return impl_->Position(); } + +void LimitedReader::set_return_status_when_blocked(Status status) { + return_status_when_blocked_ = status; +} + +void LimitedReader::set_single_read_limit(std::size_t max_num_bytes) { + single_read_limit_ = max_num_bytes; +} + +void LimitedReader::set_single_skip_limit(std::uint64_t max_num_bytes) { + single_skip_limit_ = max_num_bytes; +} + +void LimitedReader::set_total_read_limit(std::size_t max_num_bytes) { + total_read_limit_ = max_num_bytes; +} + +void LimitedReader::set_total_skip_limit(std::uint64_t max_num_bytes) { + total_skip_limit_ = max_num_bytes; +} + +void LimitedReader::set_total_read_skip_limit(std::uint64_t max_num_bytes) { + total_read_skip_limit_ = max_num_bytes; +} + +} // namespace webm diff --git a/webm_parser/test_utils/limited_reader.h b/webm_parser/test_utils/limited_reader.h new file mode 100644 index 0000000..d7f37ef --- /dev/null +++ b/webm_parser/test_utils/limited_reader.h @@ -0,0 +1,115 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef TEST_UTILS_LIMITED_READER_H_ +#define TEST_UTILS_LIMITED_READER_H_ + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <memory> + +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// An adapter that uses an underlying reader to read data, but with added +// limitations on how much data can be read/skipped. Its primary use is for +// testing APIs that consume a reader to make sure they gracefully handle +// arbitrary reading failures. +class LimitedReader : public Reader { + public: + LimitedReader() = delete; + LimitedReader(const LimitedReader&) = delete; + LimitedReader& operator=(const LimitedReader&) = delete; + + LimitedReader(LimitedReader&&) = default; + LimitedReader& operator=(LimitedReader&&) = default; + + explicit LimitedReader(std::unique_ptr<Reader> impl); + + // Reads data using the internal reader, but limits the number of bytes that + // can be read based on the settings of this LimitedReader. If this reader has + // reached its cap of maximum number of bytes allowed to be read, the chosen + // status will be returned. + Status Read(std::size_t num_to_read, std::uint8_t* buffer, + std::uint64_t* num_actually_read) override; + + // Skips data using the internal reader, but limits the number of bytes that + // can be skipped based on the settings of this LimitedReader. If this reader + // has reached its cap of maximum number of bytes allowed to be skipped, the + // chosen status will be returned. + Status Skip(std::uint64_t num_to_skip, + std::uint64_t* num_actually_skipped) override; + + std::uint64_t Position() const override; + + // Sets the status that should be returned when the reader reaches its cap of + // maximum number of bytes that can be read/skipped and cannot read/skip any + // more bytes. By default, this reader will return Status::kWouldBlock when + // this maximum limit is hit. + void set_return_status_when_blocked(Status status); + + // Sets the total number of bytes that can be read in a single call to Read. + void set_single_read_limit(std::size_t max_num_bytes); + + // Sets the total number of bytes that can be skipped in a single call to + // Skip. + void set_single_skip_limit(std::uint64_t max_num_bytes); + + // Sets the total number of bytes that can be read by the reader with Read. + // This total is considered to be cumulative for reads, but not skips. + // Setting this to std::numeric_limits<std::size_t>::max() will result in no + // extra limitation being imposed on reads. + void set_total_read_limit(std::size_t max_num_bytes); + + // Sets the total number of bytes that can be skipped by the reader with Skip. + // This total is considered to be cumulative for skips, but not reads. + // Setting this to std::numeric_limits<std::uint64_t>::max() will result in no + // extra limitation being imposed on skips. + void set_total_skip_limit(std::uint64_t max_num_bytes); + + // Sets the total number of bytes that can be read/skipped by the reader. + // This total is considered to be cumulative between reads and skips. + // Setting this to std::numeric_limits<std::uint64_t>::max() will result in no + // extra limitation being imposed on reads/skips. + void set_total_read_skip_limit(std::uint64_t max_num_bytes); + + private: + // The maximum number of bytes to let a single call to Read return. + std::size_t single_read_limit_ = std::numeric_limits<std::size_t>::max(); + + // The maximum number of bytes to let a single call to Skip return. + std::uint64_t single_skip_limit_ = std::numeric_limits<std::uint64_t>::max(); + + // The total maximum number of bytes that can be read with multiple calls to + // Read. + std::size_t total_read_limit_ = std::numeric_limits<std::size_t>::max(); + + // The total maximum number of bytes that can be skipped with multiple calls + // to Skip. + std::uint64_t total_skip_limit_ = std::numeric_limits<std::uint64_t>::max(); + + // The total maximum number of bytes that can be read or skipped with multiple + // calls to Read and/or Skip. + std::uint64_t total_read_skip_limit_ = + std::numeric_limits<std::uint64_t>::max(); + + // The status to return when the reader has reached is maximum limit for + // Read/Skip and cannot read or skip any data. + Status return_status_when_blocked_ = Status(Status::kWouldBlock); + + // The actual reader that does the real reading/skipping. + std::unique_ptr<Reader> impl_; +}; + +} // namespace webm + +#endif // TEST_UTILS_LIMITED_READER_H_ diff --git a/webm_parser/test_utils/mock_callback.h b/webm_parser/test_utils/mock_callback.h new file mode 100644 index 0000000..e4e9912 --- /dev/null +++ b/webm_parser/test_utils/mock_callback.h @@ -0,0 +1,247 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef TEST_UTILS_MOCK_CALLBACK_H_ +#define TEST_UTILS_MOCK_CALLBACK_H_ + +#include <cstdint> + +#include "gmock/gmock.h" + +#include "webm/callback.h" +#include "webm/dom_types.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// A simple version of Callback that can be used with Google Mock. By default, +// the mocked methods will call through to the corresponding Callback methods. +class MockCallback : public Callback { + public: + MockCallback() { + using testing::_; + using testing::Invoke; + + ON_CALL(*this, OnElementBegin(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnElementBeginConcrete)); + ON_CALL(*this, OnUnknownElement(_, _, _)) + .WillByDefault(Invoke(this, &MockCallback::OnUnknownElementConcrete)); + ON_CALL(*this, OnEbml(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnEbmlConcrete)); + ON_CALL(*this, OnVoid(_, _, _)) + .WillByDefault(Invoke(this, &MockCallback::OnVoidConcrete)); + ON_CALL(*this, OnSegmentBegin(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnSegmentBeginConcrete)); + ON_CALL(*this, OnSeek(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnSeekConcrete)); + ON_CALL(*this, OnInfo(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnInfoConcrete)); + ON_CALL(*this, OnClusterBegin(_, _, _)) + .WillByDefault(Invoke(this, &MockCallback::OnClusterBeginConcrete)); + ON_CALL(*this, OnSimpleBlockBegin(_, _, _)) + .WillByDefault(Invoke(this, &MockCallback::OnSimpleBlockBeginConcrete)); + ON_CALL(*this, OnSimpleBlockEnd(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnSimpleBlockEndConcrete)); + ON_CALL(*this, OnBlockGroupBegin(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnBlockGroupBeginConcrete)); + ON_CALL(*this, OnBlockBegin(_, _, _)) + .WillByDefault(Invoke(this, &MockCallback::OnBlockBeginConcrete)); + ON_CALL(*this, OnBlockEnd(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnBlockEndConcrete)); + ON_CALL(*this, OnBlockGroupEnd(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnBlockGroupEndConcrete)); + ON_CALL(*this, OnFrame(_, _, _)) + .WillByDefault(Invoke(this, &MockCallback::OnFrameConcrete)); + ON_CALL(*this, OnClusterEnd(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnClusterEndConcrete)); + ON_CALL(*this, OnTrackEntry(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnTrackEntryConcrete)); + ON_CALL(*this, OnCuePoint(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnCuePointConcrete)); + ON_CALL(*this, OnEditionEntry(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnEditionEntryConcrete)); + ON_CALL(*this, OnTag(_, _)) + .WillByDefault(Invoke(this, &MockCallback::OnTagConcrete)); + ON_CALL(*this, OnSegmentEnd(_)) + .WillByDefault(Invoke(this, &MockCallback::OnSegmentEndConcrete)); + } + + // Mocks for methods from Callback. + MOCK_METHOD2(OnElementBegin, + Status(const ElementMetadata& metadata, Action* action)); + + MOCK_METHOD3(OnUnknownElement, + Status(const ElementMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining)); + + MOCK_METHOD2(OnEbml, + Status(const ElementMetadata& metadata, const Ebml& ebml)); + + MOCK_METHOD3(OnVoid, Status(const ElementMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining)); + + MOCK_METHOD2(OnSegmentBegin, + Status(const ElementMetadata& metadata, Action* action)); + + MOCK_METHOD2(OnSeek, + Status(const ElementMetadata& metadata, const Seek& seek)); + + MOCK_METHOD2(OnInfo, + Status(const ElementMetadata& metadata, const Info& info)); + + MOCK_METHOD3(OnClusterBegin, Status(const ElementMetadata& metadata, + const Cluster& cluster, Action* action)); + + MOCK_METHOD3(OnSimpleBlockBegin, + Status(const ElementMetadata& metadata, + const SimpleBlock& simple_block, Action* action)); + + MOCK_METHOD2(OnSimpleBlockEnd, Status(const ElementMetadata& metadata, + const SimpleBlock& simple_block)); + + MOCK_METHOD2(OnBlockGroupBegin, + Status(const ElementMetadata& metadata, Action* action)); + + MOCK_METHOD3(OnBlockBegin, Status(const ElementMetadata& metadata, + const Block& block, Action* action)); + + MOCK_METHOD2(OnBlockEnd, + Status(const ElementMetadata& metadata, const Block& block)); + + MOCK_METHOD2(OnBlockGroupEnd, Status(const ElementMetadata& metadata, + const BlockGroup& block_group)); + + MOCK_METHOD3(OnFrame, Status(const FrameMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining)); + + MOCK_METHOD2(OnClusterEnd, + Status(const ElementMetadata& metadata, const Cluster& cluster)); + + MOCK_METHOD2(OnTrackEntry, Status(const ElementMetadata& metadata, + const TrackEntry& track_entry)); + + MOCK_METHOD2(OnCuePoint, Status(const ElementMetadata& metadata, + const CuePoint& cue_point)); + + MOCK_METHOD2(OnEditionEntry, Status(const ElementMetadata& metadata, + const EditionEntry& edition_entry)); + + MOCK_METHOD2(OnTag, Status(const ElementMetadata& metadata, const Tag& tag)); + + MOCK_METHOD1(OnSegmentEnd, Status(const ElementMetadata& metadata)); + + // Concrete implementations that the corresponding mocked method may call, + // provided for convenience. These methods just call through to the + // corrensponding methods in Callback, and provide an convenient way for the + // MockCallback to exhibit the same behavior as Callback. + Status OnElementBeginConcrete(const ElementMetadata& metadata, + Action* action) { + return Callback::OnElementBegin(metadata, action); + } + + Status OnUnknownElementConcrete(const ElementMetadata& metadata, + Reader* reader, + std::uint64_t* bytes_remaining) { + return Callback::OnUnknownElement(metadata, reader, bytes_remaining); + } + + Status OnEbmlConcrete(const ElementMetadata& metadata, const Ebml& ebml) { + return Callback::OnEbml(metadata, ebml); + } + + Status OnVoidConcrete(const ElementMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining) { + return Callback::OnVoid(metadata, reader, bytes_remaining); + } + + Status OnSegmentBeginConcrete(const ElementMetadata& metadata, + Action* action) { + return Callback::OnSegmentBegin(metadata, action); + } + + Status OnSeekConcrete(const ElementMetadata& metadata, const Seek& seek) { + return Callback::OnSeek(metadata, seek); + } + + Status OnInfoConcrete(const ElementMetadata& metadata, const Info& info) { + return Callback::OnInfo(metadata, info); + } + + Status OnClusterBeginConcrete(const ElementMetadata& metadata, + const Cluster& cluster, Action* action) { + return Callback::OnClusterBegin(metadata, cluster, action); + } + + Status OnSimpleBlockBeginConcrete(const ElementMetadata& metadata, + const SimpleBlock& simple_block, + Action* action) { + return Callback::OnSimpleBlockBegin(metadata, simple_block, action); + } + + Status OnSimpleBlockEndConcrete(const ElementMetadata& metadata, + const SimpleBlock& simple_block) { + return Callback::OnSimpleBlockEnd(metadata, simple_block); + } + + Status OnBlockGroupBeginConcrete(const ElementMetadata& metadata, + Action* action) { + return Callback::OnBlockGroupBegin(metadata, action); + } + + Status OnBlockBeginConcrete(const ElementMetadata& metadata, + const Block& block, Action* action) { + return Callback::OnBlockBegin(metadata, block, action); + } + + Status OnBlockEndConcrete(const ElementMetadata& metadata, + const Block& block) { + return Callback::OnBlockEnd(metadata, block); + } + + Status OnBlockGroupEndConcrete(const ElementMetadata& metadata, + const BlockGroup& block_group) { + return Callback::OnBlockGroupEnd(metadata, block_group); + } + + Status OnFrameConcrete(const FrameMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining) { + return Callback::OnFrame(metadata, reader, bytes_remaining); + } + + Status OnClusterEndConcrete(const ElementMetadata& metadata, + const Cluster& cluster) { + return Callback::OnClusterEnd(metadata, cluster); + } + + Status OnTrackEntryConcrete(const ElementMetadata& metadata, + const TrackEntry& track_entry) { + return Callback::OnTrackEntry(metadata, track_entry); + } + + Status OnCuePointConcrete(const ElementMetadata& metadata, + const CuePoint& cue_point) { + return Callback::OnCuePoint(metadata, cue_point); + } + + Status OnEditionEntryConcrete(const ElementMetadata& metadata, + const EditionEntry& edition_entry) { + return Callback::OnEditionEntry(metadata, edition_entry); + } + + Status OnTagConcrete(const ElementMetadata& metadata, const Tag& tag) { + return Callback::OnTag(metadata, tag); + } + + Status OnSegmentEndConcrete(const ElementMetadata& metadata) { + return Callback::OnSegmentEnd(metadata); + } +}; + +} // namespace webm + +#endif // TEST_UTILS_MOCK_CALLBACK_H_ diff --git a/webm_parser/test_utils/parser_test.h b/webm_parser/test_utils/parser_test.h new file mode 100644 index 0000000..61d35b2 --- /dev/null +++ b/webm_parser/test_utils/parser_test.h @@ -0,0 +1,108 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef TEST_UTILS_PARSER_TEST_H_ +#define TEST_UTILS_PARSER_TEST_H_ + +#include <cstdint> +#include <new> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/limited_reader.h" +#include "test_utils/mock_callback.h" +#include "webm/buffer_reader.h" +#include "webm/reader.h" +#include "webm/status.h" + +namespace webm { + +// Base class for unit tests that test an instance of the Parser inteface. The +// template parameter T is the parser class being tested. +template <typename T> +class ParserTest : public testing::Test { + public: + // Sets the reader's internal buffer to the given buffer. + virtual void SetReaderData(std::vector<std::uint8_t> data) { + reader_ = BufferReader(std::move(data)); + } + + // Destroys and recreates the parser, forwarding the arguments to the + // constructor. This is primarily useful for tests that require the parser to + // have different constructor parameters. + template <typename... Args> + void ResetParser(Args&&... args) { + parser_.~T(); + new (&parser_) T(std::forward<Args>(args)...); + } + + // Calls Feed() on the parser, making sure it completes successfully and reads + // size number of bytes. + virtual void ParseAndVerify(std::uint64_t size) { + std::uint64_t num_bytes_read = 0; + const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read); + ASSERT_EQ(Status::kOkCompleted, status.code); + ASSERT_EQ(size, num_bytes_read); + } + + // Calls Feed() on the parser, making sure it completes successfully and reads + // all the data available in the reader. + virtual void ParseAndVerify() { ParseAndVerify(reader_.size()); } + + // Similar to ParseAndVerify(), but instead artificially limits the reader to + // providing one byte per call to Feed(). If Feed() returns + // Status::kWouldBlock or Status::kOkPartial, Feed() will be called again + // (feeding it another byte). + virtual void IncrementalParseAndVerify() { + const std::uint64_t expected_num_bytes_read = reader_.size(); + + webm::LimitedReader limited_reader( + std::unique_ptr<webm::Reader>(new BufferReader(std::move(reader_)))); + + Status status; + std::uint64_t num_bytes_read = 0; + do { + limited_reader.set_total_read_skip_limit(1); + std::uint64_t local_num_bytes_read = 0; + status = parser_.Feed(&callback_, &limited_reader, &local_num_bytes_read); + num_bytes_read += local_num_bytes_read; + const std::uint64_t kMinBytesRead = 1; + ASSERT_GE(kMinBytesRead, local_num_bytes_read); + } while (status.code == Status::kWouldBlock || + status.code == Status::kOkPartial); + + ASSERT_EQ(Status::kOkCompleted, status.code); + ASSERT_EQ(expected_num_bytes_read, num_bytes_read); + } + + // Calls Feed() on the parser, making sure it returns the expected status + // code. + virtual void ParseAndExpectResult(Status::Code expected) { + std::uint64_t num_bytes_read = 0; + const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read); + ASSERT_EQ(expected, status.code); + } + + protected: + // These members are protected (not private) so unit tests have access to + // them. This is intentional. + + // The parser that the unit tests will be testing. + T parser_; + + // The callback that is used during parsing. + testing::NiceMock<MockCallback> callback_; + + // The reader used for feeding data into the parser. + BufferReader reader_; +}; + +} // namespace webm + +#endif // TEST_UTILS_PARSER_TEST_H_ diff --git a/webm_parser/tests/audio_parser_test.cc b/webm_parser/tests/audio_parser_test.cc new file mode 100644 index 0000000..3a609b0 --- /dev/null +++ b/webm_parser/tests/audio_parser_test.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/audio_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::Audio; +using webm::AudioParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class AudioParserTest : public ElementParserTest<AudioParser, Id::kAudio> {}; + +TEST_F(AudioParserTest, DefaultParse) { + ParseAndVerify(); + + const Audio audio = parser_.value(); + + EXPECT_FALSE(audio.sampling_frequency.is_present()); + EXPECT_EQ(8000, audio.sampling_frequency.value()); + + EXPECT_FALSE(audio.output_frequency.is_present()); + EXPECT_EQ(8000, audio.output_frequency.value()); + + EXPECT_FALSE(audio.channels.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value()); + + EXPECT_FALSE(audio.bit_depth.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value()); +} + +TEST_F(AudioParserTest, DefaultValues) { + SetReaderData({ + 0xB5, // ID = 0x85 (SamplingFrequency). + 0x40, 0x00, // Size = 0. + + 0x78, 0xB5, // ID = 0x78B5 (OutputSamplingFrequency). + 0x80, // Size = 0. + + 0x9F, // ID = 0x9F (Channels). + 0x40, 0x00, // Size = 0. + + 0x62, 0x64, // ID = 0x6264 (BitDepth). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const Audio audio = parser_.value(); + + EXPECT_TRUE(audio.sampling_frequency.is_present()); + EXPECT_EQ(8000, audio.sampling_frequency.value()); + + EXPECT_TRUE(audio.output_frequency.is_present()); + EXPECT_EQ(8000, audio.output_frequency.value()); + + EXPECT_TRUE(audio.channels.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value()); + + EXPECT_TRUE(audio.bit_depth.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value()); +} + +TEST_F(AudioParserTest, CustomValues) { + SetReaderData({ + 0xB5, // ID = 0x85 (SamplingFrequency). + 0x84, // Size = 4. + 0x3F, 0x80, 0x00, 0x00, // Body (value = 1.0f). + + 0x78, 0xB5, // ID = 0x78B5 (OutputSamplingFrequency). + 0x84, // Size = 4. + 0x3F, 0xDD, 0xB3, 0xD7, // Body (value = 1.73205077648162841796875f). + + 0x9F, // ID = 0x9F (Channels). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0x62, 0x64, // ID = 0x6264 (BitDepth). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + }); + + ParseAndVerify(); + + const Audio audio = parser_.value(); + + EXPECT_TRUE(audio.sampling_frequency.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.sampling_frequency.value()); + + EXPECT_TRUE(audio.output_frequency.is_present()); + EXPECT_EQ(1.73205077648162841796875, audio.output_frequency.value()); + + EXPECT_TRUE(audio.channels.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), audio.channels.value()); + + EXPECT_TRUE(audio.bit_depth.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.bit_depth.value()); +} + +TEST_F(AudioParserTest, AbsentOutputSamplingFrequency) { + SetReaderData({ + 0xB5, // ID = 0x85 (SamplingFrequency). + 0x84, // Size = 4. + 0x3F, 0x80, 0x00, 0x00, // Body (value = 1.0f). + }); + + ParseAndVerify(); + + const Audio audio = parser_.value(); + + EXPECT_TRUE(audio.sampling_frequency.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.sampling_frequency.value()); + + EXPECT_FALSE(audio.output_frequency.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.output_frequency.value()); + + EXPECT_FALSE(audio.channels.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value()); + + EXPECT_FALSE(audio.bit_depth.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value()); +} + +TEST_F(AudioParserTest, DefaultOutputSamplingFrequency) { + SetReaderData({ + 0xB5, // ID = 0x85 (SamplingFrequency). + 0x84, // Size = 4. + 0x3F, 0x80, 0x00, 0x00, // Body (value = 1.0f). + + 0x78, 0xB5, // ID = 0x78B5 (OutputSamplingFrequency). + 0x10, 0x00, 0x00, 0x00, // Size = 0. + }); + + ParseAndVerify(); + + const Audio audio = parser_.value(); + + EXPECT_TRUE(audio.sampling_frequency.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.sampling_frequency.value()); + + EXPECT_TRUE(audio.output_frequency.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.output_frequency.value()); + + EXPECT_FALSE(audio.channels.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value()); + + EXPECT_FALSE(audio.bit_depth.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value()); +} + +} // namespace diff --git a/webm_parser/tests/bit_utils_test.cc b/webm_parser/tests/bit_utils_test.cc new file mode 100644 index 0000000..b3e3132 --- /dev/null +++ b/webm_parser/tests/bit_utils_test.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/bit_utils.h" + +#include "gtest/gtest.h" + +using webm::CountLeadingZeros; + +namespace { + +class BitUtilsTest : public testing::Test {}; + +TEST_F(BitUtilsTest, CountLeadingZeros) { + EXPECT_EQ(8, CountLeadingZeros(0x00)); + EXPECT_EQ(4, CountLeadingZeros(0x0f)); + EXPECT_EQ(0, CountLeadingZeros(0xf0)); +} + +} // namespace diff --git a/webm_parser/tests/block_additions_parser_test.cc b/webm_parser/tests/block_additions_parser_test.cc new file mode 100644 index 0000000..290b3f5 --- /dev/null +++ b/webm_parser/tests/block_additions_parser_test.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/block_additions_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::BlockAdditions; +using webm::BlockAdditionsParser; +using webm::BlockMore; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class BlockAdditionsParserTest + : public ElementParserTest<BlockAdditionsParser, Id::kBlockAdditions> {}; + +TEST_F(BlockAdditionsParserTest, DefaultParse) { + ParseAndVerify(); + + const BlockAdditions block_additions = parser_.value(); + + const std::size_t kExpectedBlockMoresSize = 0; + EXPECT_EQ(kExpectedBlockMoresSize, block_additions.block_mores.size()); +} + +TEST_F(BlockAdditionsParserTest, DefaultValues) { + SetReaderData({ + 0xA6, // ID = 0xA6 (BlockMore). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const BlockAdditions block_additions = parser_.value(); + + ASSERT_EQ(static_cast<std::size_t>(1), block_additions.block_mores.size()); + EXPECT_TRUE(block_additions.block_mores[0].is_present()); + EXPECT_EQ(BlockMore{}, block_additions.block_mores[0].value()); +} + +TEST_F(BlockAdditionsParserTest, CustomValues) { + SetReaderData({ + 0xA6, // ID = 0xA6 (BlockMore). + 0x83, // Size = 3. + + 0xEE, // ID = 0xEE (BlockAddID). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0xA6, // ID = 0xA6 (BlockMore). + 0x83, // Size = 3. + + 0xEE, // ID = 0xEE (BlockAddID). + 0x81, // Size = 1. + 0x03, // Body (value = 3). + }); + + ParseAndVerify(); + + const BlockAdditions block_additions = parser_.value(); + + BlockMore expected; + + ASSERT_EQ(static_cast<std::size_t>(2), block_additions.block_mores.size()); + expected.id.Set(2, true); + EXPECT_TRUE(block_additions.block_mores[0].is_present()); + EXPECT_EQ(expected, block_additions.block_mores[0].value()); + expected.id.Set(3, true); + EXPECT_TRUE(block_additions.block_mores[1].is_present()); + EXPECT_EQ(expected, block_additions.block_mores[1].value()); +} + +} // namespace diff --git a/webm_parser/tests/block_group_parser_test.cc b/webm_parser/tests/block_group_parser_test.cc new file mode 100644 index 0000000..d5b0088 --- /dev/null +++ b/webm_parser/tests/block_group_parser_test.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/block_group_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" +#include "webm/status.h" + +using testing::_; +using testing::InSequence; +using testing::NotNull; +using testing::Return; + +using webm::Block; +using webm::BlockAdditions; +using webm::BlockGroup; +using webm::BlockGroupParser; +using webm::BlockMore; +using webm::ElementParserTest; +using webm::Id; +using webm::Slices; +using webm::Status; +using webm::TimeSlice; +using webm::VirtualBlock; + +namespace { + +class BlockGroupParserTest + : public ElementParserTest<BlockGroupParser, Id::kBlockGroup> {}; + +TEST_F(BlockGroupParserTest, InvalidBlock) { + SetReaderData({ + 0xA1, // ID = 0xA1 (Block). + 0x80, // Size = 0. + }); + + EXPECT_CALL(callback_, OnBlockGroupBegin(_, _)).Times(1); + EXPECT_CALL(callback_, OnBlockGroupEnd(_, _)).Times(0); + + ParseAndExpectResult(Status::kInvalidElementSize); +} + +TEST_F(BlockGroupParserTest, InvalidVirtualBlock) { + SetReaderData({ + 0xA2, // ID = 0xA2 (BlockVirtual). + 0x80, // Size = 0. + }); + + EXPECT_CALL(callback_, OnBlockGroupBegin(_, _)).Times(1); + EXPECT_CALL(callback_, OnBlockGroupEnd(_, _)).Times(0); + + ParseAndExpectResult(Status::kInvalidElementSize); +} + +TEST_F(BlockGroupParserTest, DefaultParse) { + { + InSequence dummy; + + EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull())).Times(1); + EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, BlockGroup{})).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(BlockGroupParserTest, DefaultActionIsRead) { + { + InSequence dummy; + + // This intentionally does not set the action and relies on the parser using + // a default action value of kRead. + EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull())) + .WillOnce(Return(Status(Status::kOkCompleted))); + EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, BlockGroup{})).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(BlockGroupParserTest, DefaultValues) { + SetReaderData({ + 0xA1, // ID = 0xA1 (Block). + 0x85, // Size = 5. + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + 0x00, // Frame 0. + + 0xA2, // ID = 0xA2 (BlockVirtual). + 0x84, // Size = 4. + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + + 0x75, 0xA1, // ID = 0x75A1 (BlockAdditions). + 0x80, // Size = 0. + + 0x9B, // ID = 0x9B (BlockDuration). + 0x40, 0x00, // Size = 0. + + 0xFB, // ID = 0xFB (ReferenceBlock). + 0x40, 0x00, // Size = 0. + + 0x75, 0xA2, // ID = 0x75A2 (DiscardPadding). + 0x80, // Size = 0. + + 0x8E, // ID = 0x8E (Slices). + 0x40, 0x00, // Size = 0. + }); + + { + InSequence dummy; + + EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull())).Times(1); + + BlockGroup block_group; + // Blocks and VirtualBlocks don't have any kind of default defined, so they + // can't have an element state of kPresentAsDefault. + Block block{}; + block.track_number = 1; + block.num_frames = 1; + block.is_visible = true; + block_group.block.Set(block, true); + + EXPECT_CALL(callback_, OnBlockBegin(_, block, NotNull())).Times(1); + EXPECT_CALL(callback_, OnFrame(_, NotNull(), NotNull())).Times(1); + EXPECT_CALL(callback_, OnBlockEnd(_, block_group.block.value())).Times(1); + + VirtualBlock virtual_block{}; + virtual_block.track_number = 1; + block_group.virtual_block.Set(virtual_block, true); + + block_group.additions.Set({}, true); + block_group.duration.Set(0, true); + block_group.references.emplace_back(0, true); + block_group.discard_padding.Set(0, true); + block_group.slices.Set({}, true); + + EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, block_group)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(BlockGroupParserTest, CustomValues) { + SetReaderData({ + 0xA1, // ID = 0xA1 (Block). + 0x85, // Size = 5. + 0x82, // Track number = 2. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + 0x00, // Frame 0. + + 0xA2, // ID = 0xA2 (BlockVirtual). + 0x84, // Size = 4. + 0x83, // Track number = 3. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + + 0x75, 0xA1, // ID = 0x75A1 (BlockAdditions). + 0x83, // Size = 3. + + 0xA6, // ID = 0xA6 (BlockMore). + 0x40, 0x00, // Size = 0. + + 0x9B, // ID = 0x9B (BlockDuration). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0xFB, // ID = 0xFB (ReferenceBlock). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0xFB, // ID = 0xFB (ReferenceBlock). + 0x40, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0x75, 0xA2, // ID = 0x75A2 (DiscardPadding). + 0x81, // Size = 1. + 0xFF, // Body (value = -1). + + 0x8E, // ID = 0x8E (Slices). + 0x40, 0x03, // Size = 3. + + 0xE8, // ID = 0xE8 (TimeSlice). + 0x40, 0x00, // Size = 0. + }); + + { + InSequence dummy; + + EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull())).Times(1); + + BlockGroup block_group; + // Blocks and VirtualBlocks don't have any kind of default defined, so they + // can't have an element state of kPresentAsDefault. + Block block{}; + block.track_number = 2; + block.num_frames = 1; + block.is_visible = true; + block_group.block.Set(block, true); + + VirtualBlock virtual_block{}; + virtual_block.track_number = 3; + block_group.virtual_block.Set(virtual_block, true); + + BlockAdditions block_additions; + block_additions.block_mores.emplace_back(BlockMore{}, true); + block_group.additions.Set(block_additions, true); + + block_group.duration.Set(1, true); + block_group.references.emplace_back(1, true); + block_group.references.emplace_back(2, true); + block_group.discard_padding.Set(-1, true); + + Slices slices; + slices.slices.emplace_back(TimeSlice{}, true); + block_group.slices.Set(slices, true); + + EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, block_group)).Times(1); + } + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/block_header_parser_test.cc b/webm_parser/tests/block_header_parser_test.cc new file mode 100644 index 0000000..3f06dc3 --- /dev/null +++ b/webm_parser/tests/block_header_parser_test.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/block_header_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/parser_test.h" +#include "webm/status.h" + +using webm::BlockHeader; +using webm::BlockHeaderParser; +using webm::ParserTest; + +namespace { + +class BlockHeaderParserTest : public ParserTest<BlockHeaderParser> {}; + +TEST_F(BlockHeaderParserTest, ValidBlock) { + SetReaderData({ + 0x81, // Track number = 1. + 0x12, 0x34, // Timecode = 4660. + 0x00, // Flags. + }); + + ParseAndVerify(); + + const BlockHeader& block_header = parser_.value(); + + EXPECT_EQ(static_cast<std::uint64_t>(1), block_header.track_number); + EXPECT_EQ(0x1234, block_header.timecode); + EXPECT_EQ(0x00, block_header.flags); +} + +TEST_F(BlockHeaderParserTest, IncrementalParse) { + SetReaderData({ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // Track number = 2. + 0xFF, 0xFE, // Timecode = -2. + 0xFF, // Flags. + }); + + IncrementalParseAndVerify(); + + const BlockHeader& block_header = parser_.value(); + + EXPECT_EQ(static_cast<std::uint64_t>(2), block_header.track_number); + EXPECT_EQ(-2, block_header.timecode); + EXPECT_EQ(0xFF, block_header.flags); +} + +} // namespace diff --git a/webm_parser/tests/block_more_parser_test.cc b/webm_parser/tests/block_more_parser_test.cc new file mode 100644 index 0000000..41c892c --- /dev/null +++ b/webm_parser/tests/block_more_parser_test.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/block_more_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::BlockMore; +using webm::BlockMoreParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class BlockMoreParserTest + : public ElementParserTest<BlockMoreParser, Id::kBlockMore> {}; + +TEST_F(BlockMoreParserTest, DefaultParse) { + ParseAndVerify(); + + const BlockMore block_more = parser_.value(); + + EXPECT_FALSE(block_more.id.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), block_more.id.value()); + + EXPECT_FALSE(block_more.data.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, block_more.data.value()); +} + +TEST_F(BlockMoreParserTest, DefaultValues) { + SetReaderData({ + 0xEE, // ID = 0xEE (BlockAddID). + 0x80, // Size = 0. + + 0xA5, // ID = 0xA5 (BlockAdditional). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const BlockMore block_more = parser_.value(); + + EXPECT_TRUE(block_more.id.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), block_more.id.value()); + + EXPECT_TRUE(block_more.data.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, block_more.data.value()); +} + +TEST_F(BlockMoreParserTest, CustomValues) { + SetReaderData({ + 0xEE, // ID = 0xEE (BlockAddID). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0xA5, // ID = 0xA5 (BlockAdditional). + 0x81, // Size = 1. + 0x00, // Body. + }); + + ParseAndVerify(); + + const BlockMore block_more = parser_.value(); + + EXPECT_TRUE(block_more.id.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), block_more.id.value()); + + EXPECT_TRUE(block_more.data.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{0x00}, block_more.data.value()); +} + +} // namespace diff --git a/webm_parser/tests/block_parser_test.cc b/webm_parser/tests/block_parser_test.cc new file mode 100644 index 0000000..5e414e4 --- /dev/null +++ b/webm_parser/tests/block_parser_test.cc @@ -0,0 +1,865 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/block_parser.h" + +#include <cstdint> +#include <memory> +#include <type_traits> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "src/parser_utils.h" +#include "test_utils/element_parser_test.h" +#include "webm/id.h" +#include "webm/status.h" + +using testing::_; +using testing::Between; +using testing::DoAll; +using testing::Exactly; +using testing::InSequence; +using testing::Invoke; +using testing::NotNull; +using testing::Return; +using testing::SetArgPointee; + +using webm::Action; +using webm::Block; +using webm::BlockParser; +using webm::ElementParserTest; +using webm::FrameMetadata; +using webm::Id; +using webm::kUnknownElementSize; +using webm::Lacing; +using webm::ReadByte; +using webm::Reader; +using webm::SimpleBlock; +using webm::SimpleBlockParser; +using webm::Status; + +namespace { + +// Represents a single block and its expected parsing results for use in tests. +struct TestData { + // Block data. + std::vector<std::uint8_t> data; + + // Expected results. + std::uint64_t expected_track_number; + std::int16_t expected_timecode; + Lacing expected_lacing; + bool expected_is_visible; + bool expected_is_key_frame; + bool expected_is_discardable; + int expected_num_frames; + + std::uint64_t expected_frame_start_position; + std::vector<std::uint64_t> expected_frame_sizes; +}; + +// Test data for an EBML-laced block containing only one frame. +const TestData ebml_lacing_one_frame = { + // Data. + { + 0x81, // Track number = 1. + 0x00, + 0x00, // Timecode = 0. + 0x86, // Flags = key_frame | ebml_lacing. + 0x00, // Lace count - 1 = 0 (1 frame). + + // Lace data (1 frame). + // Frame 0. + 0x00, + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kEbml, // expected_lacing + true, // expected_is_visible + true, // expected_is_key_frame + false, // expected_is_discardable + 1, // expected_num_frames + + 5, // expected_frame_start_position + // expected_frame_sizes + {1}, +}; + +// Test data for a Xiph-laced block containing only one frame. +const TestData xiph_lacing_one_frame = { + // Data. + { + 0x81, // Track number = 1. + 0x00, + 0x00, // Timecode = 0. + 0x82, // Flags = key_frame | xiph_lacing. + 0x00, // Lace count - 1 = 0 (1 frame). + + // Lace data (1 frame). + // Frame 0. + 0x00, + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kXiph, // expected_lacing + true, // expected_is_visible + true, // expected_is_key_frame + false, // expected_is_discardable + 1, // expected_num_frames + + 5, // expected_frame_start_position + // expected_frame_sizes + {1}, +}; + +// Test data for a fixed-laced block containing only one frame. +const TestData fixed_lacing_one_frame = { + // Data. + { + 0x81, // Track number = 1. + 0x00, + 0x00, // Timecode = 0. + 0x84, // Flags = key_frame | fixed_lacing. + 0x00, // Lace count - 1 = 0 (1 frame). + + // Lace data (1 frame). + 0x00, + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kFixed, // expected_lacing + true, // expected_is_visible + true, // expected_is_key_frame + false, // expected_is_discardable + 1, // expected_num_frames + + 5, // expected_frame_start_position + // expected_frame_sizes + {1}, +}; + +// Test data for an EBML-laced block. +// clang-format off +const TestData ebml_lacing = { + // Data. + { + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x86, // Flags = key_frame | ebml_lacing. + 0x05, // Lace count - 1 = 5 (6 frames). + + // Lace data (6 frames). + 0xFF, // Lace 0 size = 127. + 0x5F, 0x81, // Lace 1 size = 1. + 0xC0, // Lace 2 size = 2. + 0xFF, // Lace 3 size = 66. + 0x81, // Lace 4 size = 4. + // Lace 5 size inferred to be 5. + + // Lace data (6 frames). + // Frame 0. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // Frame 1. + 0x01, + + // Frame 2. + 0x02, 0x02, + + // Frame 3. + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + + // Frame 4. + 0x04, 0x04, 0x04, 0x04, + + // Frame 5. + 0x05, 0x05, 0x05, 0x05, 0x05, + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kEbml, // expected_lacing + true, // expected_is_visible + true, // expected_is_key_frame + false, // expected_is_discardable + 6, // expected_num_frames + + 11, // expected_frame_start_position + // expected_frame_sizes + {127, 1, 2, 66, 4, 5}, +}; + +// Test data for a Xiph-laced block. +const TestData xiph_lacing = { + // Data. + { + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x82, // Flags = key_frame | xiph_lacing. + 0x03, // Lace count - 1 = 3 (4 frames). + + // Lace sizes. + 0xFF, 0xFF, 0x00, // Lace 0 size = 510. + 0xFF, 0x01, // Lace 1 size = 256. + 0x02, // Lace 2 size = 2. + // Lace 3 size inferred to be 3. + + // Lace data (4 frames). + // Frame 0. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // Frame 1. + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, + + // Frame 2. + 0x02, 0x02, + + // Frame 3. + 0x03, 0x03, 0x03, + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kXiph, // expected_lacing + true, // expected_is_visible + true, // expected_is_key_frame + false, // expected_is_discardable + 4, // expected_num_frames + + 11, // expected_frame_start_position + // expected_frame_sizes + {510, 256, 2, 3}, +}; +// clang-format on + +// Test data for a fixed-laced block. +const TestData fixed_lacing = { + // Data. + { + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x84, // Flags = key_frame | fixed_lacing. + 0x03, // Lace count - 1 = 3 (4 frames). + + // Lace data (4 frames). + 0x00, 0x00, // Frame 0. + 0x01, 0x01, // Frame 1. + 0x02, 0x02, // Frame 2. + 0x03, 0x03, // Frame 3. + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kFixed, // expected_lacing + true, // expected_is_visible + true, // expected_is_key_frame + false, // expected_is_discardable + 4, // expected_num_frames + + 5, // expected_frame_start_position + // expected_frame_sizes + {2, 2, 2, 2}, +}; + +// Test data for a block that has no lacing. +const TestData no_lacing = { + // Data. + { + 0x40, 0x01, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x80, // Flags = key_frame. + + // Lace data (1 frame). + 0x00, 0x00, 0x00, // Frame 0. + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kNone, // expected_lacing + true, // expected_is_visible + true, // expected_is_key_frame + false, // expected_is_discardable + 1, // expected_num_frames + + 5, // expected_frame_start_position + // expected_frame_sizes + {3}, +}; + +// Test data for a block that has no flags set in the header. +const TestData no_flags = { + // Data. + { + 0x81, // Track number = 1. + 0x00, + 0x00, // Timecode = 0. + 0x00, // Flags = 0. + + // Lace data (1 frame). + 0x00, + }, + + // Expected results. + 1, // expected_track_number + 0, // expected_timecode + Lacing::kNone, // expected_lacing + true, // expected_is_visible + false, // expected_is_key_frame + false, // expected_is_discardable + 1, // expected_num_frames + + 4, // expected_frame_start_position + // expected_frame_sizes + {1}, +}; + +// Test data for a block that has all Block (ID = Id::kBlock (0xA1)) flags set +// in the header (and no other flags). +const TestData block_flags = { + // Data. + { + 0x82, // Track number = 2. + 0xFE, + 0xDC, // Timecode = -292. + 0x08, // Flags = invisible. + + // Lace data (1 frame). + 0x00, + }, + + // Expected results. + 2, // expected_track_number + -292, // expected_timecode + Lacing::kNone, // expected_lacing + false, // expected_is_visible + false, // expected_is_key_frame + false, // expected_is_discardable + 1, // expected_num_frames + + 4, // expected_frame_start_position + // expected_frame_sizes + {1}, +}; + +// Test data for a block that has all SimpleBlock (ID = Id::kSimpleBlock (0xA3)) +// flags set in the header. +const TestData simple_block_flags = { + // Data. + { + 0x41, + 0x23, // Track number = 291. + 0x12, + 0x34, // Timecode = 4660. + 0x89, // Flags = key_frame | invisible | discardable. + + // Lace data (1 frame). + 0x00, + }, + + // Expected results. + 291, // expected_track_number + 4660, // expected_timecode + Lacing::kNone, // expected_lacing + false, // expected_is_visible + true, // expected_is_key_frame + true, // expected_is_discardable + 1, // expected_num_frames + + 5, // expected_frame_start_position + // expected_frame_sizes + {1}, +}; + +// Checks that the Block matches the expected results in the TestData. +void ValidateBlock(const TestData& test_data, const Block& actual) { + EXPECT_EQ(test_data.expected_track_number, actual.track_number); + EXPECT_EQ(test_data.expected_timecode, actual.timecode); + EXPECT_EQ(test_data.expected_is_visible, actual.is_visible); + EXPECT_EQ(test_data.expected_lacing, actual.lacing); + EXPECT_EQ(test_data.expected_num_frames, actual.num_frames); +} + +// Checks that the SimpleBlock matches the expected results in the TestData. +void ValidateBlock(const TestData& test_data, const SimpleBlock& actual) { + ValidateBlock(test_data, static_cast<const Block&>(actual)); + EXPECT_EQ(test_data.expected_is_key_frame, actual.is_key_frame); + EXPECT_EQ(test_data.expected_is_discardable, actual.is_discardable); +} + +// Constructs a SimpleBlock populated with the expected results for this test. +SimpleBlock ExpectedSimpleBlock(const TestData& test_data) { + SimpleBlock expected; + expected.track_number = test_data.expected_track_number; + expected.timecode = test_data.expected_timecode; + expected.lacing = test_data.expected_lacing; + expected.is_visible = test_data.expected_is_visible; + expected.is_key_frame = test_data.expected_is_key_frame; + expected.is_discardable = test_data.expected_is_discardable; + expected.num_frames = test_data.expected_num_frames; + return expected; +} + +// Simple functor that can be used for Callback::OnFrame() that will validate +// the frame metadata and frame byte values. +struct FrameHandler { + // The expected value for the frame metadata. + FrameMetadata expected_metadata; + + // The expected value for each byte in the frame. + std::uint8_t expected_frame_byte_value; + + // Can be used for Callback::OnFrame() to consume data from the reader and + // validate the results. + Status operator()(const FrameMetadata& metadata, Reader* reader, + std::uint64_t* bytes_remaining) const { + EXPECT_EQ(expected_metadata, metadata); + + std::uint8_t frame_byte_value; + Status status; + do { + status = ReadByte(reader, &frame_byte_value); + if (!status.completed_ok()) { + break; + } + + EXPECT_EQ(expected_frame_byte_value, frame_byte_value); + + --*bytes_remaining; + } while (*bytes_remaining > 0); + + return status; + } +}; + +template <typename T, Id id> +class BasicBlockParserTest : public ElementParserTest<T, id> { + public: + // Sets expectations for a normal (i.e. successful parse) test. + void SetExpectations(const TestData& test_data, bool incremental, + bool set_action) { + InSequence dummy; + + const SimpleBlock expected_simple_block = ExpectedSimpleBlock(test_data); + const Block expected_block = ExpectedSimpleBlock(test_data); + + FrameMetadata metadata = FirstFrameMetadata(test_data); + if (std::is_same<T, SimpleBlockParser>::value) { + auto& expectation = EXPECT_CALL( + callback_, OnSimpleBlockBegin(metadata.parent_element, + expected_simple_block, NotNull())); + if (set_action) { + expectation.Times(1); + } else { + expectation.WillOnce(Return(Status(Status::kOkCompleted))); + } + EXPECT_CALL(callback_, OnBlockBegin(_, _, _)).Times(0); + } else { + auto& expectation = EXPECT_CALL( + callback_, + OnBlockBegin(metadata.parent_element, expected_block, NotNull())); + if (set_action) { + expectation.Times(1); + } else { + expectation.WillOnce(Return(Status(Status::kOkCompleted))); + } + EXPECT_CALL(callback_, OnSimpleBlockBegin(_, _, _)).Times(0); + } + + std::uint8_t expected_frame_byte_value = 0; + for (const std::uint64_t frame_size : test_data.expected_frame_sizes) { + metadata.size = frame_size; + const FrameHandler frame_handler = {metadata, expected_frame_byte_value}; + + // Incremental parsing will call OnFrame once for every byte, plus + // maybe one more time if the first call reads zero bytes (if the reader + // is blocked). + const int this_frame_size = static_cast<int>(frame_size); + EXPECT_CALL(callback_, OnFrame(metadata, NotNull(), NotNull())) + .Times(incremental ? Between(this_frame_size, this_frame_size + 1) + : Exactly(1)) + .WillRepeatedly(Invoke(frame_handler)); + + metadata.position += metadata.size; + ++expected_frame_byte_value; + } + + if (std::is_same<T, SimpleBlockParser>::value) { + EXPECT_CALL(callback_, OnSimpleBlockEnd(metadata.parent_element, + expected_simple_block)) + .Times(1); + EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0); + } else { + EXPECT_CALL(callback_, + OnBlockEnd(metadata.parent_element, expected_block)) + .Times(1); + EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0); + } + } + + // Runs a single test using the provided test data. + void RunTest(const TestData& test_data) { + SetReaderData(test_data.data); + SetExpectations(test_data, false, true); + + ParseAndVerify(); + + ValidateBlock(test_data, parser_.value()); + } + + // Same as RunTest(), except it forces parsers to parse one byte at a time. + void RunIncrementalTest(const TestData& test_data) { + SetReaderData(test_data.data); + SetExpectations(test_data, true, true); + + IncrementalParseAndVerify(); + + ValidateBlock(test_data, parser_.value()); + } + + // Tests invalid element sizes. + void TestInvalidElementSize() { + TestInit(0, Status::kInvalidElementSize); + TestInit(4, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); + } + + // Tests invalid blocks by feeding only the header of the block into the + // parser. + void TestInvalidHeaderOnly(const TestData& test_data) { + EXPECT_CALL(callback_, OnFrame(_, _, _)).Times(0); + EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0); + EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0); + + SetReaderData(test_data.data); + + ParseAndExpectResult(Status::kInvalidElementValue, + test_data.expected_frame_start_position); + } + + // Tests an invalid fixed-lace block that has inconsistent frame sizes. + void TestInvalidFixedLaceSizes() { + SetReaderData({ + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x84, // Flags = key_frame | fixed_lacing. + 0x01, // Lace count - 1 = 1 (2 frames). + + // Lace data (2 frames). + 0x00, 0x00, // Frame 0. + 0x01, // Frame 1 (invalid: inconsistent frame size). + }); + + EXPECT_CALL(callback_, OnFrame(_, _, _)).Times(0); + EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0); + EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0); + + ParseAndExpectResult(Status::kInvalidElementValue); + } + + // Tests setting the action to Action::kSkip in Callback::OnSimpleBlockBegin + // for the SimpleBlockParser. + void TestSimpleBlockSkip(const TestData& test_data) { + SetReaderData(test_data.data); + + const SimpleBlock expected_simple_block = ExpectedSimpleBlock(test_data); + const FrameMetadata metadata = FirstFrameMetadata(test_data); + + EXPECT_CALL(callback_, OnSimpleBlockBegin(metadata.parent_element, + expected_simple_block, NotNull())) + .WillOnce(Return(Status(Status::kOkPartial))) + .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnFrame(_, _, _)).Times(0); + EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0); + EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0); + + IncrementalParseAndVerify(); + } + + protected: + using ElementParserTest<T, id>::callback_; + using ElementParserTest<T, id>::IncrementalParseAndVerify; + using ElementParserTest<T, id>::metadata_; + using ElementParserTest<T, id>::ParseAndExpectResult; + using ElementParserTest<T, id>::ParseAndVerify; + using ElementParserTest<T, id>::parser_; + using ElementParserTest<T, id>::SetReaderData; + using ElementParserTest<T, id>::TestInit; + + private: + // Gets the FrameMetadata for the very first frame in the test data. + FrameMetadata FirstFrameMetadata(const TestData& test_data) { + FrameMetadata metadata; + metadata.parent_element = metadata_; + metadata.parent_element.size = test_data.data.size(); + metadata.position = test_data.expected_frame_start_position + + metadata.parent_element.position + + metadata.parent_element.header_size; + metadata.size = test_data.expected_frame_sizes[0]; + return metadata; + } +}; + +class BlockParserTest : public BasicBlockParserTest<BlockParser, Id::kBlock> {}; + +TEST_F(BlockParserTest, InvalidElementSize) { TestInvalidElementSize(); } + +TEST_F(BlockParserTest, InvalidHeaderOnlyNoLacing) { + TestInvalidHeaderOnly(no_lacing); +} + +TEST_F(BlockParserTest, InvalidHeaderOnlyFixedLacing) { + TestInvalidHeaderOnly(fixed_lacing); +} + +TEST_F(BlockParserTest, InvalidFixedLaceSizes) { TestInvalidFixedLaceSizes(); } + +TEST_F(BlockParserTest, BlockNoFlags) { RunTest(no_flags); } + +TEST_F(BlockParserTest, BlockFlags) { RunTest(block_flags); } + +TEST_F(BlockParserTest, EbmlLacingOneFrame) { RunTest(ebml_lacing_one_frame); } + +TEST_F(BlockParserTest, EbmlLacing) { RunTest(ebml_lacing); } + +TEST_F(BlockParserTest, XiphLacingOneFrame) { RunTest(xiph_lacing_one_frame); } + +TEST_F(BlockParserTest, XiphLacing) { RunTest(xiph_lacing); } + +TEST_F(BlockParserTest, FixedLacingOneFrame) { + RunTest(fixed_lacing_one_frame); +} + +TEST_F(BlockParserTest, FixedLacing) { RunTest(fixed_lacing); } + +TEST_F(BlockParserTest, NoLacing) { RunTest(no_lacing); } + +TEST_F(BlockParserTest, BlockWithPositionAndHeaderSize) { + metadata_.position = 15; + metadata_.header_size = 3; + RunTest(no_lacing); +} + +TEST_F(BlockParserTest, IncrementalBlockFlags) { + RunIncrementalTest(block_flags); +} + +TEST_F(BlockParserTest, IncrementalEbmlLacingOneFrame) { + RunIncrementalTest(ebml_lacing_one_frame); +} + +TEST_F(BlockParserTest, IncrementalEbmlLacing) { + RunIncrementalTest(ebml_lacing); +} + +TEST_F(BlockParserTest, IncrementalXiphLacingOneFrame) { + RunIncrementalTest(xiph_lacing_one_frame); +} + +TEST_F(BlockParserTest, IncrementalXiphLacing) { + RunIncrementalTest(xiph_lacing); +} + +TEST_F(BlockParserTest, IncrementalFixedLacingOneFrame) { + RunIncrementalTest(fixed_lacing_one_frame); +} + +TEST_F(BlockParserTest, IncrementalFixedLacing) { + RunIncrementalTest(fixed_lacing); +} + +TEST_F(BlockParserTest, DefaultActionIsRead) { + SetReaderData(fixed_lacing_one_frame.data); + SetExpectations(fixed_lacing_one_frame, false, false); + ParseAndVerify(); + ValidateBlock(fixed_lacing_one_frame, parser_.value()); +} + +TEST_F(BlockParserTest, IncrementalNoLacing) { RunIncrementalTest(no_lacing); } + +class SimpleBlockParserTest + : public BasicBlockParserTest<SimpleBlockParser, Id::kSimpleBlock> {}; + +TEST_F(SimpleBlockParserTest, InvalidElementSize) { TestInvalidElementSize(); } + +TEST_F(SimpleBlockParserTest, InvalidHeaderOnlyNoLacing) { + TestInvalidHeaderOnly(no_lacing); +} + +TEST_F(SimpleBlockParserTest, InvalidHeaderOnlyFixedLacing) { + TestInvalidHeaderOnly(fixed_lacing); +} + +TEST_F(SimpleBlockParserTest, InvalidFixedLaceSizes) { + TestInvalidFixedLaceSizes(); +} + +TEST_F(SimpleBlockParserTest, SimpleBlockSkip) { + TestSimpleBlockSkip(no_flags); +} + +TEST_F(SimpleBlockParserTest, SimpleBlockNoFlags) { RunTest(no_flags); } + +TEST_F(SimpleBlockParserTest, SimpleBlockFlags) { RunTest(simple_block_flags); } + +TEST_F(SimpleBlockParserTest, EbmlLacingOneFrame) { + RunTest(ebml_lacing_one_frame); +} + +TEST_F(SimpleBlockParserTest, EbmlLacing) { RunTest(ebml_lacing); } + +TEST_F(SimpleBlockParserTest, XiphLacingOneFrame) { + RunTest(xiph_lacing_one_frame); +} + +TEST_F(SimpleBlockParserTest, XiphLacing) { RunTest(xiph_lacing); } + +TEST_F(SimpleBlockParserTest, FixedLacingOneFrame) { + RunTest(fixed_lacing_one_frame); +} + +TEST_F(SimpleBlockParserTest, FixedLacing) { RunTest(fixed_lacing); } + +TEST_F(SimpleBlockParserTest, NoLacing) { RunTest(no_lacing); } + +TEST_F(BlockParserTest, SimpleBlockWithPositionAndHeaderSize) { + metadata_.position = 16; + metadata_.header_size = 4; + RunTest(no_lacing); +} + +TEST_F(SimpleBlockParserTest, IncrementalSimpleBlockFlags) { + RunIncrementalTest(simple_block_flags); +} + +TEST_F(SimpleBlockParserTest, IncrementalEbmlLacingOneFrame) { + RunIncrementalTest(ebml_lacing_one_frame); +} + +TEST_F(SimpleBlockParserTest, IncrementalEbmlLacing) { + RunIncrementalTest(ebml_lacing); +} + +TEST_F(SimpleBlockParserTest, IncrementalXiphLacingOneFrame) { + RunIncrementalTest(xiph_lacing_one_frame); +} + +TEST_F(SimpleBlockParserTest, IncrementalXiphLacing) { + RunIncrementalTest(xiph_lacing); +} + +TEST_F(SimpleBlockParserTest, IncrementalFixedLacingOneFrame) { + RunIncrementalTest(fixed_lacing_one_frame); +} + +TEST_F(SimpleBlockParserTest, IncrementalFixedLacing) { + RunIncrementalTest(fixed_lacing); +} + +TEST_F(SimpleBlockParserTest, IncrementalNoLacing) { + RunIncrementalTest(no_lacing); +} + +TEST_F(SimpleBlockParserTest, DefaultActionIsRead) { + SetReaderData(fixed_lacing_one_frame.data); + SetExpectations(fixed_lacing_one_frame, false, false); + ParseAndVerify(); + ValidateBlock(fixed_lacing_one_frame, parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/bool_parser_test.cc b/webm_parser/tests/bool_parser_test.cc new file mode 100644 index 0000000..d7f85d7 --- /dev/null +++ b/webm_parser/tests/bool_parser_test.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/bool_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/status.h" + +using webm::BoolParser; +using webm::ElementParserTest; +using webm::kUnknownElementSize; +using webm::Status; + +namespace { + +class BoolParserTest : public ElementParserTest<BoolParser> {}; + +TEST_F(BoolParserTest, InvalidSize) { + TestInit(9, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(BoolParserTest, InvalidValue) { + SetReaderData({0x02}); + ParseAndExpectResult(Status::kInvalidElementValue); + + SetReaderData({0xFF, 0xFF}); + ParseAndExpectResult(Status::kInvalidElementValue); +} + +TEST_F(BoolParserTest, CustomDefault) { + ResetParser(true); + + ParseAndVerify(); + + EXPECT_EQ(true, parser_.value()); +} + +TEST_F(BoolParserTest, ValidBool) { + ParseAndVerify(); + EXPECT_EQ(false, parser_.value()); + + SetReaderData({0x00, 0x00, 0x01}); + ParseAndVerify(); + EXPECT_EQ(true, parser_.value()); + + SetReaderData({0x00, 0x00, 0x00, 0x00, 0x00}); + ParseAndVerify(); + EXPECT_EQ(false, parser_.value()); + + SetReaderData({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}); + ParseAndVerify(); + EXPECT_EQ(true, parser_.value()); +} + +TEST_F(BoolParserTest, IncrementalParse) { + SetReaderData({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + + IncrementalParseAndVerify(); + + EXPECT_EQ(false, parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/buffer_reader_test.cc b/webm_parser/tests/buffer_reader_test.cc new file mode 100644 index 0000000..57f9d3d --- /dev/null +++ b/webm_parser/tests/buffer_reader_test.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/buffer_reader.h" + +#include <array> +#include <cstdint> + +#include "gtest/gtest.h" + +using webm::BufferReader; +using webm::Status; + +namespace { + +class BufferReaderTest : public testing::Test {}; + +TEST_F(BufferReaderTest, Assignment) { + // Test the reader to make sure it resets correctly when assigned. + std::array<std::uint8_t, 4> buffer; + std::uint64_t count; + Status status; + + BufferReader reader({}); + const std::size_t kExpectedSize = 0; + EXPECT_EQ(kExpectedSize, reader.size()); + + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); + + reader = {1, 2, 3, 4}; + EXPECT_EQ(static_cast<std::size_t>(4), reader.size()); + + status = reader.Read(2, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + reader = {5, 6, 7, 8}; + status = reader.Read(2, buffer.data() + 2, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + std::array<std::uint8_t, 4> expected = {{1, 2, 5, 6}}; + EXPECT_EQ(expected, buffer); +} + +TEST_F(BufferReaderTest, Empty) { + // Test the reader to make sure it reports EOF on empty inputs. + std::array<std::uint8_t, 1> buffer; + std::uint64_t count; + Status status; + + BufferReader reader({}); + + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); + + status = reader.Skip(1, &count); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); +} + +TEST_F(BufferReaderTest, Read) { + // Test the Read method to make sure it reads data correctly. + std::array<std::uint8_t, 15> buffer{}; + std::uint64_t count; + Status status; + + BufferReader reader({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + + status = reader.Read(5, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + + status = reader.Read(10, buffer.data() + 5, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + + std::array<std::uint8_t, 15> expected = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}; + EXPECT_EQ(expected, buffer); + + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); +} + +TEST_F(BufferReaderTest, Skip) { + // Test the Skip method to make sure it skips data correctly. + std::uint64_t count; + Status status; + + BufferReader reader({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(3), count); + + status = reader.Skip(10, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(7), count); + + status = reader.Skip(1, &count); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); +} + +TEST_F(BufferReaderTest, ReadAndSkip) { + // Test the Read and Skip methods together to make sure they interact + // correclty. + std::array<std::uint8_t, 10> buffer = {}; + std::uint64_t count; + Status status; + + BufferReader reader({9, 8, 7, 6, 5, 4, 3, 2, 1, 0}); + + status = reader.Read(5, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(3), count); + + status = reader.Read(5, buffer.data() + 5, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + std::array<std::uint8_t, 10> expected = {{9, 8, 7, 6, 5, 1, 0, 0, 0, 0}}; + EXPECT_EQ(expected, buffer); +} + +TEST_F(BufferReaderTest, Position) { + std::array<std::uint8_t, 10> buffer = {}; + std::uint64_t count; + Status status; + + BufferReader reader({9, 8, 7, 6, 5, 4, 3, 2, 1, 0}); + EXPECT_EQ(static_cast<std::uint64_t>(0), reader.Position()); + + status = reader.Read(5, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + EXPECT_EQ(static_cast<std::uint64_t>(5), reader.Position()); + + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(3), count); + EXPECT_EQ(static_cast<std::uint64_t>(8), reader.Position()); + + status = reader.Read(5, buffer.data() + 5, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + EXPECT_EQ(static_cast<std::uint64_t>(10), reader.Position()); + + std::array<std::uint8_t, 10> expected = {{9, 8, 7, 6, 5, 1, 0, 0, 0, 0}}; + EXPECT_EQ(expected, buffer); +} + +} // namespace diff --git a/webm_parser/tests/byte_parser_test.cc b/webm_parser/tests/byte_parser_test.cc new file mode 100644 index 0000000..216d624 --- /dev/null +++ b/webm_parser/tests/byte_parser_test.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/byte_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/status.h" + +using webm::BinaryParser; +using webm::ElementParserTest; +using webm::kUnknownElementSize; +using webm::Status; +using webm::StringParser; + +namespace { + +class StringParserTest : public ElementParserTest<StringParser> {}; + +TEST_F(StringParserTest, StringInvalidSize) { + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(StringParserTest, StringCustomDefault) { + ResetParser("foobar"); + + ParseAndVerify(); + + EXPECT_EQ("foobar", parser_.value()); +} + +TEST_F(StringParserTest, StringValidValue) { + ParseAndVerify(); + EXPECT_EQ("", parser_.value()); + + SetReaderData({'!'}); + ParseAndVerify(); + EXPECT_EQ("!", parser_.value()); + + SetReaderData({'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd'}); + ParseAndVerify(); + EXPECT_EQ("Hello, world", parser_.value()); +} + +TEST_F(StringParserTest, StringTrailingNulCharacters) { + // The trailing NUL characters should be trimmed. + SetReaderData({'H', 'i', '\0', '\0'}); + ParseAndVerify(); + EXPECT_EQ("Hi", parser_.value()); + + SetReaderData({'\0'}); + ParseAndVerify(); + EXPECT_EQ("", parser_.value()); +} + +TEST_F(StringParserTest, StringIncrementalParse) { + SetReaderData({'M', 'a', 't', 'r', 'o', 's', 'k', 'a'}); + + IncrementalParseAndVerify(); + + EXPECT_EQ("Matroska", parser_.value()); +} + +class BinaryParserTest : public ElementParserTest<BinaryParser> {}; + +TEST_F(BinaryParserTest, BinaryInvalidSize) { + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(BinaryParserTest, BinaryCustomDefault) { + std::vector<std::uint8_t> expected = {0x00, 0x02, 0x04, 0x06, 0x08}; + ResetParser(expected); + + ParseAndVerify(); + + EXPECT_EQ(expected, parser_.value()); +} + +TEST_F(BinaryParserTest, BinaryValidValue) { + std::vector<std::uint8_t> expected; + + ParseAndVerify(); + EXPECT_EQ(expected, parser_.value()); + + expected = {0x00}; + SetReaderData(expected); + ParseAndVerify(); + EXPECT_EQ(expected, parser_.value()); + + expected = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}; + SetReaderData(expected); + ParseAndVerify(); + EXPECT_EQ(expected, parser_.value()); + + // Unlike StringParser, the BinaryParser should not trim trailing 0-bytes. + expected = {'H', 'i', '\0', '\0'}; + SetReaderData(expected); + ParseAndVerify(); + EXPECT_EQ(expected, parser_.value()); +} + +TEST_F(BinaryParserTest, BinaryIncrementalParse) { + const std::vector<std::uint8_t> expected = { + 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}; + SetReaderData(expected); + + IncrementalParseAndVerify(); + + EXPECT_EQ(expected, parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/callback_test.cc b/webm_parser/tests/callback_test.cc new file mode 100644 index 0000000..e8aa974 --- /dev/null +++ b/webm_parser/tests/callback_test.cc @@ -0,0 +1,148 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/callback.h" + +#include <cstdint> + +#include "gtest/gtest.h" + +#include "webm/buffer_reader.h" +#include "webm/element.h" +#include "webm/status.h" + +using webm::Action; +using webm::BufferReader; +using webm::Callback; +using webm::ElementMetadata; +using webm::Reader; +using webm::Status; + +namespace { + +void TestCompletedOk(Status (Callback::*function)(const ElementMetadata&)) { + Callback callback; + ElementMetadata metadata{}; + + Status status = (callback.*function)(metadata); + EXPECT_EQ(Status::kOkCompleted, status.code); +} + +template <typename T> +void TestCompletedOk(Status (Callback::*function)(const ElementMetadata&, + const T&)) { + Callback callback; + ElementMetadata metadata{}; + T object{}; + + Status status = (callback.*function)(metadata, object); + EXPECT_EQ(Status::kOkCompleted, status.code); +} + +void TestAction(Status (Callback::*function)(const ElementMetadata&, Action*), + Action expected) { + Callback callback; + ElementMetadata metadata{}; + Action action; + + Status status = (callback.*function)(metadata, &action); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(expected, action); +} + +template <typename T> +void TestAction(Status (Callback::*function)(const ElementMetadata&, const T&, + Action*), + Action expected) { + Callback callback; + ElementMetadata metadata{}; + T t{}; + Action action; + + Status status = (callback.*function)(metadata, t, &action); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(expected, action); +} + +template <typename T> +void TestRead(Status (Callback::*function)(const T&, Reader*, std::uint64_t*)) { + Callback callback; + Status status; + T metadata{}; + BufferReader reader = {0x00, 0x01, 0x02, 0x03}; + std::uint64_t bytes_remaining = 4; + + status = (callback.*function)(metadata, &reader, &bytes_remaining); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), bytes_remaining); +} + +class CallbackTest : public testing::Test {}; + +TEST_F(CallbackTest, OnElementBegin) { + TestAction(&Callback::OnElementBegin, Action::kRead); +} + +TEST_F(CallbackTest, OnUnknownElement) { + TestRead(&Callback::OnUnknownElement); +} + +TEST_F(CallbackTest, OnEbml) { TestCompletedOk(&Callback::OnEbml); } + +TEST_F(CallbackTest, OnVoid) { TestRead(&Callback::OnVoid); } + +TEST_F(CallbackTest, OnSegmentBegin) { + TestAction(&Callback::OnSegmentBegin, Action::kRead); +} + +TEST_F(CallbackTest, OnSeek) { TestCompletedOk(&Callback::OnSeek); } + +TEST_F(CallbackTest, OnInfo) { TestCompletedOk(&Callback::OnInfo); } + +TEST_F(CallbackTest, OnClusterBegin) { + TestAction(&Callback::OnClusterBegin, Action::kRead); +} + +TEST_F(CallbackTest, OnSimpleBlockBegin) { + TestAction(&Callback::OnSimpleBlockBegin, Action::kRead); +} + +TEST_F(CallbackTest, OnSimpleBlockEnd) { + TestCompletedOk(&Callback::OnSimpleBlockEnd); +} + +TEST_F(CallbackTest, OnBlockGroupBegin) { + TestAction(&Callback::OnBlockGroupBegin, Action::kRead); +} + +TEST_F(CallbackTest, OnBlockBegin) { + TestAction(&Callback::OnBlockBegin, Action::kRead); +} + +TEST_F(CallbackTest, OnBlockEnd) { TestCompletedOk(&Callback::OnBlockEnd); } + +TEST_F(CallbackTest, OnBlockGroupEnd) { + TestCompletedOk(&Callback::OnBlockGroupEnd); +} + +TEST_F(CallbackTest, OnFrame) { TestRead(&Callback::OnFrame); } + +TEST_F(CallbackTest, OnClusterEnd) { TestCompletedOk(&Callback::OnClusterEnd); } + +TEST_F(CallbackTest, OnTrackEntry) { TestCompletedOk(&Callback::OnTrackEntry); } + +TEST_F(CallbackTest, OnCuePoint) { TestCompletedOk(&Callback::OnCuePoint); } + +TEST_F(CallbackTest, OnEditionEntry) { + TestCompletedOk(&Callback::OnEditionEntry); +} + +TEST_F(CallbackTest, OnTag) { TestCompletedOk(&Callback::OnTag); } + +TEST_F(CallbackTest, OnSegmentEnd) { TestCompletedOk(&Callback::OnSegmentEnd); } + +} // namespace diff --git a/webm_parser/tests/chapter_atom_parser_test.cc b/webm_parser/tests/chapter_atom_parser_test.cc new file mode 100644 index 0000000..5c82061 --- /dev/null +++ b/webm_parser/tests/chapter_atom_parser_test.cc @@ -0,0 +1,209 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/chapter_atom_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" +#include "webm/status.h" + +using webm::ChapterAtom; +using webm::ChapterAtomParser; +using webm::ChapterDisplay; +using webm::ElementParserTest; +using webm::Id; +using webm::Status; + +namespace { + +class ChapterAtomParserTest + : public ElementParserTest<ChapterAtomParser, Id::kChapterAtom> {}; + +TEST_F(ChapterAtomParserTest, DefaultParse) { + ParseAndVerify(); + + const ChapterAtom chapter_atom = parser_.value(); + + EXPECT_FALSE(chapter_atom.uid.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.uid.value()); + + EXPECT_FALSE(chapter_atom.string_uid.is_present()); + EXPECT_EQ("", chapter_atom.string_uid.value()); + + EXPECT_FALSE(chapter_atom.time_start.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_start.value()); + + EXPECT_FALSE(chapter_atom.time_end.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_end.value()); + + EXPECT_EQ(static_cast<std::size_t>(0), chapter_atom.displays.size()); + + EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.atoms.size()); +} + +TEST_F(ChapterAtomParserTest, DefaultValues) { + SetReaderData({ + 0x73, 0xC4, // ID = 0x73C4 (ChapterUID). + 0x80, // Size = 0. + + 0x56, 0x54, // ID = 0x73C4 (ChapterStringUID). + 0x80, // Size = 0. + + 0x91, // ID = 0x91 (ChapterTimeStart). + 0x40, 0x00, // Size = 0. + + 0x92, // ID = 0x91 (ChapterTimeEnd). + 0x40, 0x00, // Size = 0. + + 0x80, // ID = 0x80 (ChapterDisplay). + 0x40, 0x00, // Size = 0. + + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x40, 0x00, // Size = 0. + }); + + ParseAndVerify(); + + const ChapterAtom chapter_atom = parser_.value(); + + EXPECT_TRUE(chapter_atom.uid.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.uid.value()); + + EXPECT_TRUE(chapter_atom.string_uid.is_present()); + EXPECT_EQ("", chapter_atom.string_uid.value()); + + EXPECT_TRUE(chapter_atom.time_start.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_start.value()); + + EXPECT_TRUE(chapter_atom.time_end.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_end.value()); + + ASSERT_EQ(static_cast<std::uint64_t>(1), chapter_atom.displays.size()); + EXPECT_TRUE(chapter_atom.displays[0].is_present()); + EXPECT_EQ(ChapterDisplay{}, chapter_atom.displays[0].value()); + + ASSERT_EQ(static_cast<std::uint64_t>(1), chapter_atom.atoms.size()); + EXPECT_TRUE(chapter_atom.atoms[0].is_present()); + EXPECT_EQ(ChapterAtom{}, chapter_atom.atoms[0].value()); +} + +TEST_F(ChapterAtomParserTest, CustomValues) { + SetReaderData({ + 0x73, 0xC4, // ID = 0x73C4 (ChapterUID). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x56, 0x54, // ID = 0x73C4 (ChapterStringUID). + 0x81, // Size = 1. + 0x41, // Body (value = "A"). + + 0x91, // ID = 0x91 (ChapterTimeStart). + 0x40, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0x92, // ID = 0x91 (ChapterTimeEnd). + 0x40, 0x01, // Size = 1. + 0x03, // Body (value = 3). + + 0x80, // ID = 0x80 (ChapterDisplay). + 0x40, 0x04, // Size = 4. + + 0x85, // ID = 0x85 (ChapString). + 0x40, 0x01, // Size = 1. + 0x42, // Body (value = "B"). + + 0x80, // ID = 0x80 (ChapterDisplay). + 0x40, 0x04, // Size = 4. + + 0x85, // ID = 0x85 (ChapString). + 0x40, 0x01, // Size = 1. + 0x43, // Body (value = "C"). + + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x40, 0x12, // Size = 18. + + 0x73, 0xC4, // ID = 0x73C4 (ChapterUID). + 0x81, // Size = 1. + 0x04, // Body (value = 4). + + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x40, 0x04, // Size = 4. + + 0x73, 0xC4, // ID = 0x73C4 (ChapterUID). + 0x81, // Size = 1. + 0x05, // Body (value = 5). + + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x40, 0x04, // Size = 4. + + 0x73, 0xC4, // ID = 0x73C4 (ChapterUID). + 0x81, // Size = 1. + 0x06, // Body (value = 6). + }); + + ParseAndVerify(); + + const ChapterAtom chapter_atom = parser_.value(); + + EXPECT_TRUE(chapter_atom.uid.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), chapter_atom.uid.value()); + + EXPECT_TRUE(chapter_atom.string_uid.is_present()); + EXPECT_EQ("A", chapter_atom.string_uid.value()); + + EXPECT_TRUE(chapter_atom.time_start.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), chapter_atom.time_start.value()); + + EXPECT_TRUE(chapter_atom.time_end.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(3), chapter_atom.time_end.value()); + + ChapterDisplay expected_chapter_display; + + ASSERT_EQ(static_cast<std::size_t>(2), chapter_atom.displays.size()); + expected_chapter_display.string.Set("B", true); + EXPECT_TRUE(chapter_atom.displays[0].is_present()); + EXPECT_EQ(expected_chapter_display, chapter_atom.displays[0].value()); + expected_chapter_display.string.Set("C", true); + EXPECT_TRUE(chapter_atom.displays[1].is_present()); + EXPECT_EQ(expected_chapter_display, chapter_atom.displays[1].value()); + + ChapterAtom expected_chapter_atom; + expected_chapter_atom.uid.Set(4, true); + + ChapterAtom tmp_atom{}; + tmp_atom.uid.Set(5, true); + expected_chapter_atom.atoms.emplace_back(tmp_atom, true); + tmp_atom.uid.Set(6, true); + expected_chapter_atom.atoms.emplace_back(tmp_atom, true); + + ASSERT_EQ(static_cast<std::size_t>(1), chapter_atom.atoms.size()); + EXPECT_TRUE(chapter_atom.atoms[0].is_present()); + EXPECT_EQ(expected_chapter_atom, chapter_atom.atoms[0].value()); +} + +TEST_F(ChapterAtomParserTest, ExceedMaxRecursionDepth) { + ResetParser(1); + + SetReaderData({ + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x80, // Size = 0. + }); + ParseAndVerify(); + + SetReaderData({ + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x82, // Size = 2. + + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x80, // Size = 0. + }); + ParseAndExpectResult(Status::kExceededRecursionDepthLimit); +} + +} // namespace diff --git a/webm_parser/tests/chapter_display_parser_test.cc b/webm_parser/tests/chapter_display_parser_test.cc new file mode 100644 index 0000000..224c1da --- /dev/null +++ b/webm_parser/tests/chapter_display_parser_test.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/chapter_display_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ChapterDisplay; +using webm::ChapterDisplayParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class ChapterDisplayParserTest + : public ElementParserTest<ChapterDisplayParser, Id::kChapterDisplay> {}; + +TEST_F(ChapterDisplayParserTest, DefaultParse) { + ParseAndVerify(); + + const ChapterDisplay chapter_display = parser_.value(); + + EXPECT_FALSE(chapter_display.string.is_present()); + EXPECT_EQ("", chapter_display.string.value()); + + ASSERT_EQ(static_cast<std::uint64_t>(1), chapter_display.languages.size()); + EXPECT_FALSE(chapter_display.languages[0].is_present()); + EXPECT_EQ("eng", chapter_display.languages[0].value()); + + EXPECT_EQ(static_cast<std::size_t>(0), chapter_display.countries.size()); +} + +TEST_F(ChapterDisplayParserTest, DefaultValues) { + SetReaderData({ + 0x85, // ID = 0x85 (ChapString). + 0x40, 0x00, // Size = 0. + + 0x43, 0x7C, // ID = 0x437C (ChapLanguage). + 0x80, // Size = 0. + + 0x43, 0x7E, // ID = 0x437E (ChapCountry). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const ChapterDisplay chapter_display = parser_.value(); + + EXPECT_TRUE(chapter_display.string.is_present()); + EXPECT_EQ("", chapter_display.string.value()); + + ASSERT_EQ(static_cast<std::size_t>(1), chapter_display.languages.size()); + EXPECT_TRUE(chapter_display.languages[0].is_present()); + EXPECT_EQ("eng", chapter_display.languages[0].value()); + + ASSERT_EQ(static_cast<std::size_t>(1), chapter_display.countries.size()); + EXPECT_TRUE(chapter_display.countries[0].is_present()); + EXPECT_EQ("", chapter_display.countries[0].value()); +} + +TEST_F(ChapterDisplayParserTest, CustomValues) { + SetReaderData({ + 0x85, // ID = 0x85 (ChapString). + 0x40, 0x05, // Size = 5. + 0x68, 0x65, 0x6C, 0x6C, 0x6F, // Body (value = "hello"). + + 0x43, 0x7C, // ID = 0x437C (ChapLanguage). + 0x85, // Size = 5. + 0x6C, 0x61, 0x6E, 0x67, 0x30, // body (value = "lang0"). + + 0x43, 0x7E, // ID = 0x437E (ChapCountry). + 0x85, // Size = 5. + 0x61, 0x72, 0x65, 0x61, 0x30, // Body (value = "area0"). + + 0x43, 0x7C, // ID = 0x437C (ChapLanguage). + 0x85, // Size = 5. + 0x6C, 0x61, 0x6E, 0x67, 0x31, // body (value = "lang1"). + + 0x43, 0x7C, // ID = 0x437C (ChapLanguage). + 0x85, // Size = 5. + 0x6C, 0x61, 0x6E, 0x67, 0x32, // body (value = "lang2"). + + 0x43, 0x7E, // ID = 0x437E (ChapCountry). + 0x85, // Size = 5. + 0x61, 0x72, 0x65, 0x61, 0x31, // Body (value = "area1"). + }); + + ParseAndVerify(); + + const ChapterDisplay chapter_display = parser_.value(); + + EXPECT_TRUE(chapter_display.string.is_present()); + EXPECT_EQ("hello", chapter_display.string.value()); + + ASSERT_EQ(static_cast<std::size_t>(3), chapter_display.languages.size()); + EXPECT_TRUE(chapter_display.languages[0].is_present()); + EXPECT_EQ("lang0", chapter_display.languages[0].value()); + EXPECT_TRUE(chapter_display.languages[1].is_present()); + EXPECT_EQ("lang1", chapter_display.languages[1].value()); + EXPECT_TRUE(chapter_display.languages[2].is_present()); + EXPECT_EQ("lang2", chapter_display.languages[2].value()); + + ASSERT_EQ(static_cast<std::size_t>(2), chapter_display.countries.size()); + EXPECT_TRUE(chapter_display.countries[0].is_present()); + EXPECT_EQ("area0", chapter_display.countries[0].value()); + EXPECT_TRUE(chapter_display.countries[1].is_present()); + EXPECT_EQ("area1", chapter_display.countries[1].value()); +} + +} // namespace diff --git a/webm_parser/tests/chapters_parser_test.cc b/webm_parser/tests/chapters_parser_test.cc new file mode 100644 index 0000000..b84deff --- /dev/null +++ b/webm_parser/tests/chapters_parser_test.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/chapters_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ChaptersParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class ChaptersParserTest + : public ElementParserTest<ChaptersParser, Id::kChapters> {}; + +TEST_F(ChaptersParserTest, DefaultValues) { + ParseAndVerify(); + + SetReaderData({ + 0x45, 0xB9, // ID = 0x45B9 (EditionEntry). + 0x80, // Size = 0. + }); + ParseAndVerify(); +} + +TEST_F(ChaptersParserTest, RepeatedValues) { + SetReaderData({ + 0x45, 0xB9, // ID = 0x45B9 (EditionEntry). + 0x84, // Size = 4. + + 0x45, 0xBC, // ID = 0x45BC (EditionUID). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x45, 0xB9, // ID = 0x45B9 (EditionEntry). + 0x84, // Size = 4. + + 0x45, 0xBC, // ID = 0x45BC (EditionUID). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/cluster_parser_test.cc b/webm_parser/tests/cluster_parser_test.cc new file mode 100644 index 0000000..fb879b6 --- /dev/null +++ b/webm_parser/tests/cluster_parser_test.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/cluster_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/id.h" + +using testing::_; +using testing::DoAll; +using testing::InSequence; +using testing::NotNull; +using testing::Return; +using testing::SetArgPointee; + +using webm::Action; +using webm::Ancestory; +using webm::BlockGroup; +using webm::Cluster; +using webm::ClusterParser; +using webm::ElementMetadata; +using webm::ElementParserTest; +using webm::Id; +using webm::SimpleBlock; +using webm::Status; + +namespace { + +class ClusterParserTest + : public ElementParserTest<ClusterParser, Id::kCluster> {}; + +TEST_F(ClusterParserTest, DefaultParse) { + { + InSequence dummy; + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, Cluster{}, NotNull())) + .Times(1); + + EXPECT_CALL(callback_, OnClusterEnd(metadata_, Cluster{})).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(ClusterParserTest, DefaultActionIsRead) { + { + InSequence dummy; + + // This intentionally does not set the action and relies on the parser using + // a default action value of kRead. + EXPECT_CALL(callback_, OnClusterBegin(metadata_, Cluster{}, NotNull())) + .WillOnce(Return(Status(Status::kOkCompleted))); + EXPECT_CALL(callback_, OnClusterEnd(metadata_, Cluster{})).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(ClusterParserTest, DefaultValues) { + SetReaderData({ + 0xE7, // ID = 0xE7 (Timecode). + 0x40, 0x00, // Size = 0. + + 0xAB, // ID = 0xAB (PrevSize). + 0x40, 0x00, // Size = 0. + + 0xA3, // ID = 0xA3 (SimpleBlock). + 0x85, // Size = 5. + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + 0x00, // Frame 0. + + 0xA0, // ID = 0xA0 (BlockGroup). + 0x40, 0x00, // Size = 0. + }); + + { + InSequence dummy; + + Cluster cluster{}; + cluster.timecode.Set(0, true); + cluster.previous_size.Set(0, true); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + SimpleBlock simple_block{}; + simple_block.track_number = 1; + simple_block.num_frames = 1; + simple_block.is_visible = true; + cluster.simple_blocks.emplace_back(simple_block, true); + + BlockGroup block_group{}; + cluster.block_groups.emplace_back(block_group, true); + + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(ClusterParserTest, CustomValues) { + SetReaderData({ + 0xE7, // ID = 0xE7 (Timecode). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0xAB, // ID = 0xAB (PrevSize). + 0x40, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0xA3, // ID = 0xA3 (SimpleBlock). + 0x85, // Size = 5. + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + 0x00, // Frame 0. + + 0xA3, // ID = 0xA3 (SimpleBlock). + 0x85, // Size = 5. + 0x82, // Track number = 2. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + 0x00, // Frame 0. + + 0xA0, // ID = 0xA0 (BlockGroup). + 0x40, 0x04, // Size = 4. + + 0x9B, // ID = 0x9B (BlockDuration). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0xA0, // ID = 0xA0 (BlockGroup). + 0x40, 0x04, // Size = 4. + + 0x9B, // ID = 0x9B (BlockDuration). + 0x40, 0x01, // Size = 1. + 0x02, // Body (value = 2). + }); + + { + InSequence dummy; + + Cluster cluster{}; + cluster.timecode.Set(1, true); + cluster.previous_size.Set(2, true); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + SimpleBlock simple_block{}; + simple_block.num_frames = 1; + simple_block.is_visible = true; + + simple_block.track_number = 1; + cluster.simple_blocks.emplace_back(simple_block, true); + simple_block.track_number = 2; + cluster.simple_blocks.emplace_back(simple_block, true); + + BlockGroup block_group{}; + block_group.duration.Set(1, true); + cluster.block_groups.emplace_back(block_group, true); + block_group.duration.Set(2, true); + cluster.block_groups.emplace_back(block_group, true); + + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(ClusterParserTest, SkipOnClusterBegin) { + { + InSequence dummy; + + EXPECT_CALL(callback_, OnClusterBegin(_, _, _)).Times(0); + + EXPECT_CALL(callback_, OnBlockGroupBegin(_, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(1); + } + + ElementMetadata child_metadata = {Id::kBlockGroup, 0, 0, 0}; + + Ancestory ancestory; + ASSERT_TRUE(Ancestory::ById(child_metadata.id, &ancestory)); + // Skip the Segment and Cluster ancestors. + ancestory = ancestory.next().next(); + + parser_.InitAfterSeek(ancestory, child_metadata); + + std::uint64_t num_bytes_read = 0; + const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(reader_.size(), num_bytes_read); +} + +TEST_F(ClusterParserTest, SkipSimpleBlock) { + SetReaderData({ + 0xA3, // ID = 0xA3 (SimpleBlock). + 0x85, // Size = 5. + 0x81, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags = 0. + 0x00, // Frame 0. + }); + + { + InSequence dummy; + + Cluster cluster{}; + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + SimpleBlock simple_block{}; + simple_block.num_frames = 1; + simple_block.is_visible = true; + simple_block.track_number = 1; + + EXPECT_CALL(callback_, OnSimpleBlockBegin(_, simple_block, NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(ClusterParserTest, SkipBlockGroup) { + SetReaderData({ + 0xA0, // ID = 0xA0 (BlockGroup). + 0x40, 0x04, // Size = 4. + + 0x9B, // ID = 0x9B (BlockDuration). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + }); + + { + InSequence dummy; + + Cluster cluster{}; + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + EXPECT_CALL(callback_, OnBlockGroupBegin(_, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/colour_parser_test.cc b/webm_parser/tests/colour_parser_test.cc new file mode 100644 index 0000000..0fe31f0 --- /dev/null +++ b/webm_parser/tests/colour_parser_test.cc @@ -0,0 +1,285 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/colour_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::Colour; +using webm::ColourParser; +using webm::ElementParserTest; +using webm::Id; +using webm::MasteringMetadata; +using webm::MatrixCoefficients; +using webm::Primaries; +using webm::Range; +using webm::TransferCharacteristics; + +namespace { + +class ColourParserTest : public ElementParserTest<ColourParser, Id::kColour> {}; + +TEST_F(ColourParserTest, DefaultParse) { + ParseAndVerify(); + + const Colour colour = parser_.value(); + + EXPECT_FALSE(colour.matrix_coefficients.is_present()); + EXPECT_EQ(MatrixCoefficients::kUnspecified, + colour.matrix_coefficients.value()); + + EXPECT_FALSE(colour.bits_per_channel.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.bits_per_channel.value()); + + EXPECT_FALSE(colour.chroma_subsampling_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_x.value()); + + EXPECT_FALSE(colour.chroma_subsampling_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_y.value()); + + EXPECT_FALSE(colour.cb_subsampling_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_x.value()); + + EXPECT_FALSE(colour.cb_subsampling_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_y.value()); + + EXPECT_FALSE(colour.chroma_siting_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_x.value()); + + EXPECT_FALSE(colour.chroma_siting_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_y.value()); + + EXPECT_FALSE(colour.range.is_present()); + EXPECT_EQ(Range::kUnspecified, colour.range.value()); + + EXPECT_FALSE(colour.transfer_characteristics.is_present()); + EXPECT_EQ(TransferCharacteristics::kUnspecified, + colour.transfer_characteristics.value()); + + EXPECT_FALSE(colour.primaries.is_present()); + EXPECT_EQ(Primaries::kUnspecified, colour.primaries.value()); + + EXPECT_FALSE(colour.max_cll.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_cll.value()); + + EXPECT_FALSE(colour.max_fall.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_fall.value()); + + EXPECT_FALSE(colour.mastering_metadata.is_present()); + EXPECT_EQ(MasteringMetadata{}, colour.mastering_metadata.value()); +} + +TEST_F(ColourParserTest, DefaultValues) { + SetReaderData({ + 0x55, 0xB1, // ID = 0x55B1 (MatrixCoefficients). + 0x80, // Size = 0. + + 0x55, 0xB2, // ID = 0x55B2 (BitsPerChannel). + 0x80, // Size = 0. + + 0x55, 0xB3, // ID = 0x55B3 (ChromaSubsamplingHorz). + 0x80, // Size = 0. + + 0x55, 0xB4, // ID = 0x55B4 (ChromaSubsamplingVert). + 0x80, // Size = 0. + + 0x55, 0xB5, // ID = 0x55B5 (CbSubsamplingHorz). + 0x80, // Size = 0. + + 0x55, 0xB6, // ID = 0x55B6 (CbSubsamplingVert). + 0x80, // Size = 0. + + 0x55, 0xB7, // ID = 0x55B7 (ChromaSitingHorz). + 0x80, // Size = 0. + + 0x55, 0xB8, // ID = 0x55B8 (ChromaSitingVert). + 0x80, // Size = 0. + + 0x55, 0xB9, // ID = 0x55B9 (Range). + 0x80, // Size = 0. + + 0x55, 0xBA, // ID = 0x55BA (TransferCharacteristics). + 0x80, // Size = 0. + + 0x55, 0xBB, // ID = 0x55BB (Primaries). + 0x80, // Size = 0. + + 0x55, 0xBC, // ID = 0x55BC (MaxCLL). + 0x80, // Size = 0. + + 0x55, 0xBD, // ID = 0x55BD (MaxFALL). + 0x80, // Size = 0. + + 0x55, 0xD0, // ID = 0x55D0 (MasteringMetadata). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const Colour colour = parser_.value(); + + EXPECT_TRUE(colour.matrix_coefficients.is_present()); + EXPECT_EQ(MatrixCoefficients::kUnspecified, + colour.matrix_coefficients.value()); + + EXPECT_TRUE(colour.bits_per_channel.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.bits_per_channel.value()); + + EXPECT_TRUE(colour.chroma_subsampling_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_x.value()); + + EXPECT_TRUE(colour.chroma_subsampling_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_y.value()); + + EXPECT_TRUE(colour.cb_subsampling_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_x.value()); + + EXPECT_TRUE(colour.cb_subsampling_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_y.value()); + + EXPECT_TRUE(colour.chroma_siting_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_x.value()); + + EXPECT_TRUE(colour.chroma_siting_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_y.value()); + + EXPECT_TRUE(colour.range.is_present()); + EXPECT_EQ(Range::kUnspecified, colour.range.value()); + + EXPECT_TRUE(colour.transfer_characteristics.is_present()); + EXPECT_EQ(TransferCharacteristics::kUnspecified, + colour.transfer_characteristics.value()); + + EXPECT_TRUE(colour.primaries.is_present()); + EXPECT_EQ(Primaries::kUnspecified, colour.primaries.value()); + + EXPECT_TRUE(colour.max_cll.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_cll.value()); + + EXPECT_TRUE(colour.max_fall.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_fall.value()); + + EXPECT_TRUE(colour.mastering_metadata.is_present()); + EXPECT_EQ(MasteringMetadata{}, colour.mastering_metadata.value()); +} + +TEST_F(ColourParserTest, CustomValues) { + SetReaderData({ + 0x55, 0xB1, // ID = 0x55B1 (MatrixCoefficients). + 0x81, // Size = 1. + 0x01, // Body (value = BT.709). + + 0x55, 0xB2, // ID = 0x55B2 (BitsPerChannel). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0x55, 0xB3, // ID = 0x55B3 (ChromaSubsamplingHorz). + 0x81, // Size = 1. + 0x03, // Body (value = 3). + + 0x55, 0xB4, // ID = 0x55B4 (ChromaSubsamplingVert). + 0x81, // Size = 1. + 0x04, // Body (value = 4). + + 0x55, 0xB5, // ID = 0x55B5 (CbSubsamplingHorz). + 0x81, // Size = 1. + 0x05, // Body (value = 5). + + 0x55, 0xB6, // ID = 0x55B6 (CbSubsamplingVert). + 0x81, // Size = 1. + 0x06, // Body (value = 6). + + 0x55, 0xB7, // ID = 0x55B7 (ChromaSitingHorz). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x55, 0xB8, // ID = 0x55B8 (ChromaSitingVert). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0x55, 0xB9, // ID = 0x55B9 (Range). + 0x81, // Size = 1. + 0x03, // Body (value = 3 (derived)). + + 0x55, 0xBA, // ID = 0x55BA (TransferCharacteristics). + 0x81, // Size = 1. + 0x04, // Body (value = BT.470‑6 System M with display gamma 2.2). + + 0x55, 0xBB, // ID = 0x55BB (Primaries). + 0x81, // Size = 1. + 0x05, // Body (value = BT.470‑6 System B, G). + + 0x55, 0xBC, // ID = 0x55BC (MaxCLL). + 0x81, // Size = 1. + 0x06, // Body (value = 6). + + 0x55, 0xBD, // ID = 0x55BD (MaxFALL). + 0x81, // Size = 1. + 0x07, // Body (value = 7). + + 0x55, 0xD0, // ID = 0x55D0 (MasteringMetadata). + 0x87, // Size = 7. + + 0x55, 0xD1, // ID = 0x55D1 (PrimaryRChromaticityX). + 0x84, // Size = 4. + 0x3F, 0x80, 0x00, 0x00, // Body (value = 1). + }); + + ParseAndVerify(); + + const Colour colour = parser_.value(); + + EXPECT_TRUE(colour.matrix_coefficients.is_present()); + EXPECT_EQ(MatrixCoefficients::kBt709, colour.matrix_coefficients.value()); + + EXPECT_TRUE(colour.bits_per_channel.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), colour.bits_per_channel.value()); + + EXPECT_TRUE(colour.chroma_subsampling_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(3), colour.chroma_subsampling_x.value()); + + EXPECT_TRUE(colour.chroma_subsampling_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(4), colour.chroma_subsampling_y.value()); + + EXPECT_TRUE(colour.cb_subsampling_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(5), colour.cb_subsampling_x.value()); + + EXPECT_TRUE(colour.cb_subsampling_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(6), colour.cb_subsampling_y.value()); + + EXPECT_TRUE(colour.chroma_siting_x.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), colour.chroma_siting_x.value()); + + EXPECT_TRUE(colour.chroma_siting_y.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), colour.chroma_siting_y.value()); + + EXPECT_TRUE(colour.range.is_present()); + EXPECT_EQ(Range::kDerived, colour.range.value()); + + EXPECT_TRUE(colour.transfer_characteristics.is_present()); + EXPECT_EQ(TransferCharacteristics::kGamma22curve, + colour.transfer_characteristics.value()); + + EXPECT_TRUE(colour.primaries.is_present()); + EXPECT_EQ(Primaries::kBt470Bg, colour.primaries.value()); + + EXPECT_TRUE(colour.max_cll.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(6), colour.max_cll.value()); + + EXPECT_TRUE(colour.max_fall.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(7), colour.max_fall.value()); + + MasteringMetadata mastering_metadata{}; + mastering_metadata.primary_r_chromaticity_x.Set(1.0, true); + EXPECT_TRUE(colour.mastering_metadata.is_present()); + EXPECT_EQ(mastering_metadata, colour.mastering_metadata.value()); +} + +} // namespace diff --git a/webm_parser/tests/content_enc_aes_settings_parser_test.cc b/webm_parser/tests/content_enc_aes_settings_parser_test.cc new file mode 100644 index 0000000..4c021ef --- /dev/null +++ b/webm_parser/tests/content_enc_aes_settings_parser_test.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/content_enc_aes_settings_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::AesSettingsCipherMode; +using webm::ContentEncAesSettings; +using webm::ContentEncAesSettingsParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class ContentEncAesSettingsParserTest + : public ElementParserTest<ContentEncAesSettingsParser, + Id::kContentEncAesSettings> {}; + +TEST_F(ContentEncAesSettingsParserTest, DefaultParse) { + ParseAndVerify(); + + const ContentEncAesSettings content_enc_aes_settings = parser_.value(); + + EXPECT_FALSE(content_enc_aes_settings.aes_settings_cipher_mode.is_present()); + EXPECT_EQ(AesSettingsCipherMode::kCtr, + content_enc_aes_settings.aes_settings_cipher_mode.value()); +} + +TEST_F(ContentEncAesSettingsParserTest, DefaultValues) { + SetReaderData({ + 0x47, 0xE8, // ID = 0x47E8 (AESSettingsCipherMode). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const ContentEncAesSettings content_enc_aes_settings = parser_.value(); + + EXPECT_TRUE(content_enc_aes_settings.aes_settings_cipher_mode.is_present()); + EXPECT_EQ(AesSettingsCipherMode::kCtr, + content_enc_aes_settings.aes_settings_cipher_mode.value()); +} + +TEST_F(ContentEncAesSettingsParserTest, CustomValues) { + SetReaderData({ + 0x47, 0xE8, // ID = 0x47E8 (AESSettingsCipherMode). + 0x81, // Size = 1. + 0x00, // Body (value = 0). + }); + + ParseAndVerify(); + + const ContentEncAesSettings content_enc_aes_settings = parser_.value(); + + EXPECT_TRUE(content_enc_aes_settings.aes_settings_cipher_mode.is_present()); + EXPECT_EQ(static_cast<AesSettingsCipherMode>(0), + content_enc_aes_settings.aes_settings_cipher_mode.value()); +} + +} // namespace diff --git a/webm_parser/tests/content_encoding_parser_test.cc b/webm_parser/tests/content_encoding_parser_test.cc new file mode 100644 index 0000000..c739f50 --- /dev/null +++ b/webm_parser/tests/content_encoding_parser_test.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/content_encoding_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ContentEncAlgo; +using webm::ContentEncoding; +using webm::ContentEncodingParser; +using webm::ContentEncodingType; +using webm::ContentEncryption; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class ContentEncodingParserTest + : public ElementParserTest<ContentEncodingParser, Id::kContentEncoding> {}; + +TEST_F(ContentEncodingParserTest, DefaultParse) { + ParseAndVerify(); + + const ContentEncoding content_encoding = parser_.value(); + + EXPECT_FALSE(content_encoding.order.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), content_encoding.order.value()); + + EXPECT_FALSE(content_encoding.scope.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), content_encoding.scope.value()); + + EXPECT_FALSE(content_encoding.type.is_present()); + EXPECT_EQ(ContentEncodingType::kCompression, content_encoding.type.value()); + + EXPECT_FALSE(content_encoding.encryption.is_present()); + EXPECT_EQ(ContentEncryption{}, content_encoding.encryption.value()); +} + +TEST_F(ContentEncodingParserTest, DefaultValues) { + SetReaderData({ + 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder). + 0x80, // Size = 0. + + 0x50, 0x32, // ID = 0x5032 (ContentEncodingScope). + 0x80, // Size = 0. + + 0x50, 0x33, // ID = 0x5033 (ContentEncodingType). + 0x80, // Size = 0. + + 0x50, 0x35, // ID = 0x5035 (ContentEncryption). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const ContentEncoding content_encoding = parser_.value(); + + EXPECT_TRUE(content_encoding.order.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), content_encoding.order.value()); + + EXPECT_TRUE(content_encoding.scope.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), content_encoding.scope.value()); + + EXPECT_TRUE(content_encoding.type.is_present()); + EXPECT_EQ(ContentEncodingType::kCompression, content_encoding.type.value()); + + EXPECT_TRUE(content_encoding.encryption.is_present()); + EXPECT_EQ(ContentEncryption{}, content_encoding.encryption.value()); +} + +TEST_F(ContentEncodingParserTest, CustomValues) { + SetReaderData({ + 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x50, 0x32, // ID = 0x5032 (ContentEncodingScope). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0x50, 0x33, // ID = 0x5033 (ContentEncodingType). + 0x81, // Size = 1. + 0x01, // Body (value = encryption). + + 0x50, 0x35, // ID = 0x5035 (ContentEncryption). + 0x84, // Size = 4. + + 0x47, 0xE1, // ID = 0x47E1 (ContentEncAlgo). + 0x81, // Size = 1. + 0x05, // Body (value = AES). + }); + + ParseAndVerify(); + + const ContentEncoding content_encoding = parser_.value(); + + EXPECT_TRUE(content_encoding.order.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), content_encoding.order.value()); + + EXPECT_TRUE(content_encoding.scope.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), content_encoding.scope.value()); + + EXPECT_TRUE(content_encoding.type.is_present()); + EXPECT_EQ(ContentEncodingType::kEncryption, content_encoding.type.value()); + + ContentEncryption expected; + expected.algorithm.Set(ContentEncAlgo::kAes, true); + + EXPECT_TRUE(content_encoding.encryption.is_present()); + EXPECT_EQ(expected, content_encoding.encryption.value()); +} + +} // namespace diff --git a/webm_parser/tests/content_encodings_parser_test.cc b/webm_parser/tests/content_encodings_parser_test.cc new file mode 100644 index 0000000..fedb6aa --- /dev/null +++ b/webm_parser/tests/content_encodings_parser_test.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/content_encodings_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ContentEncoding; +using webm::ContentEncodings; +using webm::ContentEncodingsParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class ContentEncodingsParserTest + : public ElementParserTest<ContentEncodingsParser, Id::kContentEncodings> { +}; + +TEST_F(ContentEncodingsParserTest, DefaultParse) { + ParseAndVerify(); + + const ContentEncodings content_encodings = parser_.value(); + + EXPECT_EQ(static_cast<std::size_t>(0), content_encodings.encodings.size()); +} + +TEST_F(ContentEncodingsParserTest, DefaultValues) { + SetReaderData({ + 0x62, 0x40, // ID = 0x6240 (ContentEncoding). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const ContentEncodings content_encodings = parser_.value(); + + ASSERT_EQ(static_cast<std::size_t>(1), content_encodings.encodings.size()); + EXPECT_TRUE(content_encodings.encodings[0].is_present()); + EXPECT_EQ(ContentEncoding{}, content_encodings.encodings[0].value()); +} + +TEST_F(ContentEncodingsParserTest, CustomValues) { + SetReaderData({ + 0x62, 0x40, // ID = 0x6240 (ContentEncoding). + 0x84, // Size = 4. + + 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x62, 0x40, // ID = 0x6240 (ContentEncoding). + 0x84, // Size = 4. + + 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); + + const ContentEncodings content_encodings = parser_.value(); + + ContentEncoding expected; + + ASSERT_EQ(static_cast<std::size_t>(2), content_encodings.encodings.size()); + expected.order.Set(1, true); + EXPECT_TRUE(content_encodings.encodings[0].is_present()); + EXPECT_EQ(expected, content_encodings.encodings[0].value()); + expected.order.Set(2, true); + EXPECT_TRUE(content_encodings.encodings[1].is_present()); + EXPECT_EQ(expected, content_encodings.encodings[1].value()); +} + +} // namespace diff --git a/webm_parser/tests/content_encryption_parser_test.cc b/webm_parser/tests/content_encryption_parser_test.cc new file mode 100644 index 0000000..a731390 --- /dev/null +++ b/webm_parser/tests/content_encryption_parser_test.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/content_encryption_parser.h" + +#include <cstdint> + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::AesSettingsCipherMode; +using webm::ContentEncAesSettings; +using webm::ContentEncAlgo; +using webm::ContentEncryption; +using webm::ContentEncryptionParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class ContentEncryptionParserTest + : public ElementParserTest<ContentEncryptionParser, + Id::kContentEncryption> {}; + +TEST_F(ContentEncryptionParserTest, DefaultParse) { + ParseAndVerify(); + + const ContentEncryption content_encryption = parser_.value(); + + EXPECT_FALSE(content_encryption.algorithm.is_present()); + EXPECT_EQ(ContentEncAlgo::kOnlySigned, content_encryption.algorithm.value()); + + EXPECT_FALSE(content_encryption.key_id.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, content_encryption.key_id.value()); + + EXPECT_FALSE(content_encryption.aes_settings.is_present()); + EXPECT_EQ(ContentEncAesSettings{}, content_encryption.aes_settings.value()); +} + +TEST_F(ContentEncryptionParserTest, DefaultValues) { + SetReaderData({ + 0x47, 0xE1, // ID = 0x47E1 (ContentEncAlgo). + 0x80, // Size = 0. + + 0x47, 0xE2, // ID = 0x47E2 (ContentEncKeyID). + 0x80, // Size = 0. + + 0x47, 0xE7, // ID = 0x47E7 (ContentEncAESSettings). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const ContentEncryption content_encryption = parser_.value(); + + EXPECT_TRUE(content_encryption.algorithm.is_present()); + EXPECT_EQ(ContentEncAlgo::kOnlySigned, content_encryption.algorithm.value()); + + EXPECT_TRUE(content_encryption.key_id.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, content_encryption.key_id.value()); + + EXPECT_TRUE(content_encryption.aes_settings.is_present()); + EXPECT_EQ(ContentEncAesSettings{}, content_encryption.aes_settings.value()); +} + +TEST_F(ContentEncryptionParserTest, CustomValues) { + SetReaderData({ + 0x47, 0xE1, // ID = 0x47E1 (ContentEncAlgo). + 0x81, // Size = 1. + 0x05, // Body (value = AES). + + 0x47, 0xE2, // ID = 0x47E2 (ContentEncKeyID). + 0x81, // Size = 1. + 0x00, // Body. + + 0x47, 0xE7, // ID = 0x47E7 (ContentEncAESSettings). + 0x84, // Size = 4. + + 0x47, 0xE8, // ID = 0x47E8 (AESSettingsCipherMode). + 0x81, // Size = 1. + 0x00, // Body (value = 0). + }); + + ParseAndVerify(); + + const ContentEncryption content_encryption = parser_.value(); + + EXPECT_TRUE(content_encryption.algorithm.is_present()); + EXPECT_EQ(ContentEncAlgo::kAes, content_encryption.algorithm.value()); + + EXPECT_TRUE(content_encryption.key_id.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{0x00}, content_encryption.key_id.value()); + + ContentEncAesSettings expected; + expected.aes_settings_cipher_mode.Set(static_cast<AesSettingsCipherMode>(0), + true); + EXPECT_TRUE(content_encryption.aes_settings.is_present()); + EXPECT_EQ(expected, content_encryption.aes_settings.value()); +} + +} // namespace diff --git a/webm_parser/tests/cue_point_parser_test.cc b/webm_parser/tests/cue_point_parser_test.cc new file mode 100644 index 0000000..9f3d48a --- /dev/null +++ b/webm_parser/tests/cue_point_parser_test.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/cue_point_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::CuePoint; +using webm::CuePointParser; +using webm::CueTrackPositions; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class CuePointParserTest + : public ElementParserTest<CuePointParser, Id::kCuePoint> {}; + +TEST_F(CuePointParserTest, DefaultParse) { + EXPECT_CALL(callback_, OnCuePoint(metadata_, CuePoint{})).Times(1); + + ParseAndVerify(); +} + +TEST_F(CuePointParserTest, DefaultValues) { + SetReaderData({ + 0xB3, // ID = 0xB3 (CueTime). + 0x80, // Size = 0. + + 0xB7, // ID = 0xB7 (CueTrackPositions). + 0x80, // Size = 0. + }); + + CuePoint cue_point; + cue_point.time.Set(0, true); + cue_point.cue_track_positions.emplace_back(); + cue_point.cue_track_positions[0].Set({}, true); + + EXPECT_CALL(callback_, OnCuePoint(metadata_, cue_point)).Times(1); + + ParseAndVerify(); +} + +TEST_F(CuePointParserTest, CustomValues) { + SetReaderData({ + 0xB3, // ID = 0xB3 (CueTime). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xB7, // ID = 0xB7 (CueTrackPositions). + 0x83, // Size = 3. + + 0xF1, // ID = 0xF1 (CueClusterPosition). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0xB7, // ID = 0xB7 (CueTrackPositions). + 0x83, // Size = 3. + + 0xF7, // ID = 0xF7 (CueTrack). + 0x81, // Size = 1. + 0x03, // Body (value = 3). + }); + + CuePoint cue_point; + cue_point.time.Set(1, true); + CueTrackPositions cue_track_positions; + cue_track_positions.cluster_position.Set(2, true); + cue_point.cue_track_positions.emplace_back(cue_track_positions, true); + cue_track_positions = {}; + cue_track_positions.track.Set(3, true); + cue_point.cue_track_positions.emplace_back(cue_track_positions, true); + + EXPECT_CALL(callback_, OnCuePoint(metadata_, cue_point)).Times(1); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/cue_track_positions_parser_test.cc b/webm_parser/tests/cue_track_positions_parser_test.cc new file mode 100644 index 0000000..d7b6844 --- /dev/null +++ b/webm_parser/tests/cue_track_positions_parser_test.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/cue_track_positions_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::CueTrackPositions; +using webm::CueTrackPositionsParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class CueTrackPositionsParserTest + : public ElementParserTest<CueTrackPositionsParser, + Id::kCueTrackPositions> {}; + +TEST_F(CueTrackPositionsParserTest, DefaultParse) { + ParseAndVerify(); + + const CueTrackPositions cue_track_positions = parser_.value(); + + EXPECT_FALSE(cue_track_positions.track.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), cue_track_positions.track.value()); + + EXPECT_FALSE(cue_track_positions.cluster_position.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), + cue_track_positions.cluster_position.value()); + + EXPECT_FALSE(cue_track_positions.relative_position.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), + cue_track_positions.relative_position.value()); + + EXPECT_FALSE(cue_track_positions.duration.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), + cue_track_positions.duration.value()); + + EXPECT_FALSE(cue_track_positions.block_number.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), + cue_track_positions.block_number.value()); +} + +TEST_F(CueTrackPositionsParserTest, DefaultValues) { + SetReaderData({ + 0xF7, // ID = 0xF7 (CueTrack). + 0x40, 0x00, // Size = 0. + + 0xF1, // ID = 0xF1 (CueClusterPosition). + 0x40, 0x00, // Size = 0. + + 0xF0, // ID = 0xF0 (CueRelativePosition). + 0x40, 0x00, // Size = 0. + + 0xB2, // ID = 0xB2 (CueDuration). + 0x40, 0x00, // Size = 0. + + 0x53, 0x78, // ID = 0x5378 (CueBlockNumber). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const CueTrackPositions cue_track_positions = parser_.value(); + + EXPECT_TRUE(cue_track_positions.track.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), cue_track_positions.track.value()); + + EXPECT_TRUE(cue_track_positions.cluster_position.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), + cue_track_positions.cluster_position.value()); + + EXPECT_TRUE(cue_track_positions.relative_position.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), + cue_track_positions.relative_position.value()); + + EXPECT_TRUE(cue_track_positions.duration.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), + cue_track_positions.duration.value()); + + EXPECT_TRUE(cue_track_positions.block_number.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), + cue_track_positions.block_number.value()); +} + +TEST_F(CueTrackPositionsParserTest, CustomValues) { + SetReaderData({ + 0xF7, // ID = 0xF7 (CueTrack). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0xF1, // ID = 0xF1 (CueClusterPosition). + 0x40, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0xF0, // ID = 0xF0 (CueRelativePosition). + 0x40, 0x01, // Size = 1. + 0x03, // Body (value = 3). + + 0xB2, // ID = 0xB2 (CueDuration). + 0x40, 0x01, // Size = 1. + 0x04, // Body (value = 4). + + 0x53, 0x78, // ID = 0x5378 (CueBlockNumber). + 0x81, // Size = 1. + 0x05, // Body (value = 5). + }); + + ParseAndVerify(); + + const CueTrackPositions cue_track_positions = parser_.value(); + + EXPECT_TRUE(cue_track_positions.track.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), cue_track_positions.track.value()); + + EXPECT_TRUE(cue_track_positions.cluster_position.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), + cue_track_positions.cluster_position.value()); + + EXPECT_TRUE(cue_track_positions.relative_position.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(3), + cue_track_positions.relative_position.value()); + + EXPECT_TRUE(cue_track_positions.duration.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(4), + cue_track_positions.duration.value()); + + EXPECT_TRUE(cue_track_positions.block_number.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(5), + cue_track_positions.block_number.value()); +} + +} // namespace diff --git a/webm_parser/tests/cues_parser_test.cc b/webm_parser/tests/cues_parser_test.cc new file mode 100644 index 0000000..e9957b3 --- /dev/null +++ b/webm_parser/tests/cues_parser_test.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/cues_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::CuesParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class CuesParserTest : public ElementParserTest<CuesParser, Id::kCues> {}; + +TEST_F(CuesParserTest, DefaultValues) { + ParseAndVerify(); + + SetReaderData({ + 0xBB, // ID = 0xBB (CuePoint). + 0x80, // Size = 0. + }); + ParseAndVerify(); +} + +TEST_F(CuesParserTest, RepeatedValues) { + SetReaderData({ + 0xBB, // ID = 0xBB (CuePoint). + 0x83, // Size = 3. + + 0xB3, // ID = 0xB3 (CueTime). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xBB, // ID = 0xBB (CuePoint). + 0x83, // Size = 3. + + 0xB3, // ID = 0xB3 (CueTime). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/date_parser_test.cc b/webm_parser/tests/date_parser_test.cc new file mode 100644 index 0000000..e81f452 --- /dev/null +++ b/webm_parser/tests/date_parser_test.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/date_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/status.h" + +using webm::DateParser; +using webm::ElementParserTest; +using webm::kUnknownElementSize; +using webm::Status; + +namespace { + +class DateParserTest : public ElementParserTest<DateParser> {}; + +TEST_F(DateParserTest, InvalidSize) { + TestInit(4, Status::kInvalidElementSize); + TestInit(9, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(DateParserTest, CustomDefault) { + ResetParser(-1); + + ParseAndVerify(); + + EXPECT_EQ(-1, parser_.value()); +} + +TEST_F(DateParserTest, ValidDate) { + ParseAndVerify(); + EXPECT_EQ(0, parser_.value()); + + SetReaderData({0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}); + ParseAndVerify(); + EXPECT_EQ(0x123456789ABCDEF0, parser_.value()); +} + +TEST_F(DateParserTest, IncrementalParse) { + SetReaderData({0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}); + + IncrementalParseAndVerify(); + + EXPECT_EQ(static_cast<std::int64_t>(0xFEDCBA9876543210), parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/ebml_parser_test.cc b/webm_parser/tests/ebml_parser_test.cc new file mode 100644 index 0000000..741f424 --- /dev/null +++ b/webm_parser/tests/ebml_parser_test.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/ebml_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::Ebml; +using webm::EbmlParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class EbmlParserTest : public ElementParserTest<EbmlParser, Id::kEbml> {}; + +TEST_F(EbmlParserTest, DefaultParse) { + EXPECT_CALL(callback_, OnEbml(metadata_, Ebml{})).Times(1); + + ParseAndVerify(); +} + +TEST_F(EbmlParserTest, DefaultValues) { + SetReaderData({ + 0x42, 0x86, // ID = 0x4286 (EBMLVersion). + 0x80, // Size = 0. + + 0x42, 0xF7, // ID = 0x42F7 (EBMLReadVersion). + 0x80, // Size = 0. + + 0x42, 0xF2, // ID = 0x42F2 (EBMLMaxIDLength). + 0x40, 0x00, // Size = 0. + + 0x42, 0xF3, // ID = 0x42F3 (EBMLMaxSizeLength). + 0x80, // Size = 0. + + 0xEC, // ID = 0xEC (Void). + 0x40, 0x00, // Size = 0. + + 0x42, 0x82, // ID = 0x4282 (DocType). + 0x40, 0x00, // Size = 0. + + 0x42, 0x87, // ID = 0x4287 (DocTypeVersion). + 0x80, // Size = 0. + + 0x42, 0x85, // ID = 0x4285 (DocTypeReadVersion). + 0x80, // Size = 0. + + 0xEC, // ID = 0xEC (Void). + 0x82, // Size = 2. + 0x01, 0x02, // Body. + }); + + Ebml ebml; + ebml.ebml_version.Set(1, true); + ebml.ebml_read_version.Set(1, true); + ebml.ebml_max_id_length.Set(4, true); + ebml.ebml_max_size_length.Set(8, true); + ebml.doc_type.Set("matroska", true); + ebml.doc_type_version.Set(1, true); + ebml.doc_type_read_version.Set(1, true); + + EXPECT_CALL(callback_, OnEbml(metadata_, ebml)).Times(1); + + ParseAndVerify(); +} + +TEST_F(EbmlParserTest, CustomValues) { + SetReaderData({ + 0x42, 0x86, // ID = 0x4286 (EBMLVersion). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0x42, 0xF7, // ID = 0x42F7 (EBMLReadVersion). + 0x81, // Size = 1. + 0x04, // Body (value = 4). + + 0x42, 0xF2, // ID = 0x42F2 (EBMLMaxIDLength). + 0x40, 0x02, // Size = 2. + 0x00, 0x02, // Body (value = 2). + + 0x42, 0xF3, // ID = 0x42F3 (EBMLMaxSizeLength). + 0x81, // Size = 1. + 0x04, // Body (value = 4). + + 0xEC, // ID = 0xEC (Void). + 0x40, 0x00, // Size = 0. + + 0x42, 0x82, // ID = 0x4282 (DocType). + 0x40, 0x02, // Size = 2. + 0x48, 0x69, // Body (value = "Hi"). + + 0x42, 0x87, // ID = 0x4287 (DocTypeVersion). + 0x81, // Size = 1. + 0xFF, // Body (value = 255). + + 0x42, 0x85, // ID = 0x4285 (DocTypeReadVersion). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + + 0xEC, // ID = 0xEC (Void). + 0x82, // Size = 2. + 0x01, 0x02, // Body. + }); + + Ebml ebml; + ebml.ebml_version.Set(2, true); + ebml.ebml_read_version.Set(4, true); + ebml.ebml_max_id_length.Set(2, true); + ebml.ebml_max_size_length.Set(4, true); + ebml.doc_type.Set("Hi", true); + ebml.doc_type_version.Set(255, true); + ebml.doc_type_read_version.Set(2, true); + + EXPECT_CALL(callback_, OnEbml(metadata_, ebml)).Times(1); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/edition_entry_parser_test.cc b/webm_parser/tests/edition_entry_parser_test.cc new file mode 100644 index 0000000..b2d137c --- /dev/null +++ b/webm_parser/tests/edition_entry_parser_test.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/edition_entry_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ChapterAtom; +using webm::EditionEntry; +using webm::EditionEntryParser; +using webm::ElementParserTest; +using webm::Id; + +namespace { + +class EditionEntryParserTest + : public ElementParserTest<EditionEntryParser, Id::kEditionEntry> {}; + +TEST_F(EditionEntryParserTest, DefaultParse) { + EXPECT_CALL(callback_, OnEditionEntry(metadata_, EditionEntry{})).Times(1); + + ParseAndVerify(); +} + +TEST_F(EditionEntryParserTest, DefaultValues) { + SetReaderData({ + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x80, // Size = 0. + }); + + EditionEntry edition_entry; + edition_entry.atoms.emplace_back(); + edition_entry.atoms[0].Set({}, true); + + EXPECT_CALL(callback_, OnEditionEntry(metadata_, edition_entry)).Times(1); + + ParseAndVerify(); +} + +TEST_F(EditionEntryParserTest, CustomValues) { + SetReaderData({ + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x40, 0x04, // Size = 4. + + 0x73, 0xC4, // ID = 0x73C4 (ChapterUID). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xB6, // ID = 0xB6 (ChapterAtom). + 0x40, 0x04, // Size = 4. + + 0x73, 0xC4, // ID = 0x73C4 (ChapterUID). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + EditionEntry edition_entry; + ChapterAtom chapter_atom; + chapter_atom.uid.Set(1, true); + edition_entry.atoms.emplace_back(chapter_atom, true); + chapter_atom.uid.Set(2, true); + edition_entry.atoms.emplace_back(chapter_atom, true); + + EXPECT_CALL(callback_, OnEditionEntry(metadata_, edition_entry)).Times(1); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/element_test.cc b/webm_parser/tests/element_test.cc new file mode 100644 index 0000000..d88ac07 --- /dev/null +++ b/webm_parser/tests/element_test.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/element.h" + +#include <string> +#include <utility> + +#include "gtest/gtest.h" + +using webm::Element; + +namespace { + +class ElementTest : public testing::Test {}; + +TEST_F(ElementTest, Construction) { + Element<int> value_initialized; + EXPECT_EQ(false, value_initialized.is_present()); + EXPECT_EQ(0, value_initialized.value()); + + Element<int> absent_custom_default(1); + EXPECT_EQ(false, absent_custom_default.is_present()); + EXPECT_EQ(1, absent_custom_default.value()); + + Element<int> present(2, true); + EXPECT_EQ(true, present.is_present()); + EXPECT_EQ(2, present.value()); +} + +TEST_F(ElementTest, Assignment) { + Element<int> e; + + e.Set(42, true); + EXPECT_EQ(true, e.is_present()); + EXPECT_EQ(42, e.value()); + + *e.mutable_value() = 0; + EXPECT_EQ(true, e.is_present()); + EXPECT_EQ(0, e.value()); +} + +} // namespace diff --git a/webm_parser/tests/float_parser_test.cc b/webm_parser/tests/float_parser_test.cc new file mode 100644 index 0000000..ffa392d --- /dev/null +++ b/webm_parser/tests/float_parser_test.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/float_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/status.h" + +using webm::ElementParserTest; +using webm::FloatParser; +using webm::kUnknownElementSize; +using webm::Status; + +namespace { + +class FloatParserTest : public ElementParserTest<FloatParser> {}; + +TEST_F(FloatParserTest, InvalidSize) { + TestInit(1, Status::kInvalidElementSize); + TestInit(5, Status::kInvalidElementSize); + TestInit(9, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(FloatParserTest, CustomDefault) { + const double sqrt2 = 1.4142135623730951454746218587388284504413604736328125; + ResetParser(sqrt2); + + ParseAndVerify(); + + EXPECT_EQ(sqrt2, parser_.value()); +} + +TEST_F(FloatParserTest, ValidFloat) { + ParseAndVerify(); + EXPECT_EQ(0, parser_.value()); + + SetReaderData({0x40, 0xC9, 0x0F, 0xDB}); + ParseAndVerify(); + EXPECT_EQ(6.283185482025146484375, parser_.value()); + + SetReaderData({0x40, 0x05, 0xBF, 0x0A, 0x8B, 0x14, 0x57, 0x69}); + ParseAndVerify(); + EXPECT_EQ(2.718281828459045090795598298427648842334747314453125, + parser_.value()); +} + +TEST_F(FloatParserTest, IncrementalParse) { + SetReaderData({0x3F, 0xF9, 0xE3, 0x77, 0x9B, 0x97, 0xF4, 0xA8}); + + IncrementalParseAndVerify(); + + EXPECT_EQ(1.6180339887498949025257388711906969547271728515625, + parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/id_element_parser_test.cc b/webm_parser/tests/id_element_parser_test.cc new file mode 100644 index 0000000..8cfbb45 --- /dev/null +++ b/webm_parser/tests/id_element_parser_test.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/id_element_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/status.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::IdElementParser; +using webm::kUnknownElementSize; +using webm::Status; + +namespace { + +class IdElementParserTest : public ElementParserTest<IdElementParser> {}; + +TEST_F(IdElementParserTest, InvalidSize) { + TestInit(0, Status::kInvalidElementSize); + TestInit(5, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(IdElementParserTest, ValidId) { + SetReaderData({0xEC}); + ParseAndVerify(); + EXPECT_EQ(Id::kVoid, parser_.value()); + + SetReaderData({0x1F, 0x43, 0xB6, 0x75}); + ParseAndVerify(); + EXPECT_EQ(Id::kCluster, parser_.value()); +} + +TEST_F(IdElementParserTest, IncrementalParse) { + SetReaderData({0x2A, 0xD7, 0xB1}); + + IncrementalParseAndVerify(); + + EXPECT_EQ(Id::kTimecodeScale, parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/id_parser_test.cc b/webm_parser/tests/id_parser_test.cc new file mode 100644 index 0000000..73d13d8 --- /dev/null +++ b/webm_parser/tests/id_parser_test.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/id_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/parser_test.h" +#include "webm/id.h" +#include "webm/status.h" + +using webm::Id; +using webm::IdParser; +using webm::ParserTest; +using webm::Status; + +namespace { + +class IdParserTest : public ParserTest<IdParser> {}; + +TEST_F(IdParserTest, InvalidId) { + SetReaderData({0x00}); + ParseAndExpectResult(Status::kInvalidElementId); + + ResetParser(); + SetReaderData({0x0F}); + ParseAndExpectResult(Status::kInvalidElementId); +} + +TEST_F(IdParserTest, EarlyEndOfFile) { + SetReaderData({0x40}); + ParseAndExpectResult(Status::kEndOfFile); +} + +TEST_F(IdParserTest, ValidId) { + SetReaderData({0x80}); + ParseAndVerify(); + EXPECT_EQ(static_cast<Id>(0x80), parser_.id()); + + ResetParser(); + SetReaderData({0x1F, 0xFF, 0xFF, 0xFF}); + ParseAndVerify(); + EXPECT_EQ(static_cast<Id>(0x1FFFFFFF), parser_.id()); +} + +TEST_F(IdParserTest, IncrementalParse) { + SetReaderData({0x1A, 0x45, 0xDF, 0xA3}); + IncrementalParseAndVerify(); + EXPECT_EQ(Id::kEbml, parser_.id()); +} + +} // namespace diff --git a/webm_parser/tests/info_parser_test.cc b/webm_parser/tests/info_parser_test.cc new file mode 100644 index 0000000..fecc33a --- /dev/null +++ b/webm_parser/tests/info_parser_test.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/info_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::Info; +using webm::InfoParser; + +namespace { + +class InfoParserTest : public ElementParserTest<InfoParser, Id::kInfo> {}; + +TEST_F(InfoParserTest, DefaultParse) { + EXPECT_CALL(callback_, OnInfo(metadata_, Info{})).Times(1); + + ParseAndVerify(); +} + +TEST_F(InfoParserTest, DefaultValues) { + SetReaderData({ + 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale). + 0x80, // Size = 0. + + 0x44, 0x89, // ID = 0x4489 (Duration). + 0x20, 0x00, 0x00, // Size = 0. + + 0x44, 0x61, // ID = 0x4461 (DateUTC). + 0x20, 0x00, 0x00, // Size = 0. + + 0x7B, 0xA9, // ID = 0x7BA9 (Title). + 0x20, 0x00, 0x00, // Size = 0. + + 0x4D, 0x80, // ID = 0x4D80 (MuxingApp). + 0x20, 0x00, 0x00, // Size = 0. + + 0x57, 0x41, // ID = 0x5741 (WritingApp). + 0x20, 0x00, 0x00, // Size = 0. + }); + + Info info; + info.timecode_scale.Set(1000000, true); + info.duration.Set(0, true); + info.date_utc.Set(0, true); + info.title.Set("", true); + info.muxing_app.Set("", true); + info.writing_app.Set("", true); + + EXPECT_CALL(callback_, OnInfo(metadata_, info)).Times(1); + + ParseAndVerify(); +} + +TEST_F(InfoParserTest, CustomValues) { + SetReaderData({ + 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x44, 0x89, // ID = 0x4489 (Duration). + 0x84, // Size = 4. + 0x4D, 0x8E, 0xF3, 0xC2, // Body (value = 299792448.0f). + + 0x44, 0x61, // ID = 0x4461 (DateUTC). + 0x88, // Size = 8. + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Body (value = -1). + + 0x7B, 0xA9, // ID = 0x7BA9 (Title). + 0x10, 0x00, 0x00, 0x03, // Size = 3. + 0x66, 0x6F, 0x6F, // Body (value = "foo"). + + 0x4D, 0x80, // ID = 0x4D80 (MuxingApp). + 0x10, 0x00, 0x00, 0x03, // Size = 3. + 0x62, 0x61, 0x72, // Body (value = "bar"). + + 0x57, 0x41, // ID = 0x5741 (WritingApp). + 0x10, 0x00, 0x00, 0x03, // Size = 3. + 0x62, 0x61, 0x7A, // Body (value = "baz"). + }); + + Info info; + info.timecode_scale.Set(1, true); + info.duration.Set(299792448.0f, true); + info.date_utc.Set(-1, true); + info.title.Set("foo", true); + info.muxing_app.Set("bar", true); + info.writing_app.Set("baz", true); + + EXPECT_CALL(callback_, OnInfo(metadata_, info)).Times(1); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/int_parser_test.cc b/webm_parser/tests/int_parser_test.cc new file mode 100644 index 0000000..c2e74e2 --- /dev/null +++ b/webm_parser/tests/int_parser_test.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/int_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/status.h" + +using webm::ElementParserTest; +using webm::kUnknownElementSize; +using webm::SignedIntParser; +using webm::Status; +using webm::UnsignedIntParser; + +namespace { + +class UnsignedIntParserTest : public ElementParserTest<UnsignedIntParser> {}; + +TEST_F(UnsignedIntParserTest, UnsignedInvalidSize) { + TestInit(9, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(UnsignedIntParserTest, UnsignedCustomDefault) { + ResetParser(1); + + ParseAndVerify(); + + EXPECT_EQ(static_cast<std::uint64_t>(1), parser_.value()); +} + +TEST_F(UnsignedIntParserTest, UnsignedValidInt) { + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0), parser_.value()); + + SetReaderData({0x01, 0x02, 0x03}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0x010203), parser_.value()); + + SetReaderData({0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0xFFFFFFFFFF), parser_.value()); + + SetReaderData({0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0x123456789ABCDEF0), parser_.value()); +} + +TEST_F(UnsignedIntParserTest, UnsignedIncrementalParse) { + SetReaderData({0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}); + + IncrementalParseAndVerify(); + + EXPECT_EQ(static_cast<std::uint64_t>(0xFEDCBA9876543210), parser_.value()); +} + +class SignedIntParserTest : public ElementParserTest<SignedIntParser> {}; + +TEST_F(SignedIntParserTest, SignedInvalidSize) { + TestInit(9, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(SignedIntParserTest, SignedCustomDefault) { + ResetParser(-1); + + ParseAndVerify(); + + EXPECT_EQ(-1, parser_.value()); +} + +TEST_F(SignedIntParserTest, SignedValidPositiveInt) { + ParseAndVerify(); + EXPECT_EQ(0, parser_.value()); + + SetReaderData({0x7f}); + ParseAndVerify(); + EXPECT_EQ(0x7f, parser_.value()); + + SetReaderData({0x12, 0xD6, 0x87}); + ParseAndVerify(); + EXPECT_EQ(1234567, parser_.value()); + + SetReaderData({0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}); + ParseAndVerify(); + EXPECT_EQ(0x123456789ABCDEF0, parser_.value()); +} + +TEST_F(SignedIntParserTest, SignedValidNegativeInt) { + SetReaderData({0xFF}); + ParseAndVerify(); + EXPECT_EQ(-1, parser_.value()); + + SetReaderData({0xF8, 0xA4, 0x32, 0xEB}); + ParseAndVerify(); + EXPECT_EQ(-123456789, parser_.value()); +} + +TEST_F(SignedIntParserTest, SignedIncrementalParse) { + SetReaderData({0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x11}); + + IncrementalParseAndVerify(); + + EXPECT_EQ(-81985529216486895, parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/istream_reader_test.cc b/webm_parser/tests/istream_reader_test.cc new file mode 100644 index 0000000..d9c7293 --- /dev/null +++ b/webm_parser/tests/istream_reader_test.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/istream_reader.h" + +#include <algorithm> +#include <array> +#include <cstdint> +#include <sstream> +#include <type_traits> + +#include "gtest/gtest.h" + +using webm::IstreamReader; +using webm::Status; + +namespace { + +// Creates a std::array from a string literal (and omits the trailing +// NUL-character). +template <std::size_t N> +std::array<std::uint8_t, N - 1> ArrayFromString(const char (&string)[N]) { + std::array<std::uint8_t, N - 1> array; + std::copy_n(string, N - 1, array.begin()); + return array; +} + +class IstreamReaderTest : public testing::Test {}; + +TEST_F(IstreamReaderTest, Read) { + std::array<std::uint8_t, 15> buffer{}; + std::uint64_t count; + Status status; + + IstreamReader reader = + IstreamReader::Emplace<std::istringstream>("abcdefghij"); + + status = reader.Read(5, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + + status = reader.Read(10, buffer.data() + 5, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + + std::array<std::uint8_t, 15> expected = + ArrayFromString("abcdefghij\0\0\0\0\0"); + EXPECT_EQ(expected, buffer); + + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); +} + +TEST_F(IstreamReaderTest, Skip) { + std::uint64_t count; + Status status; + + IstreamReader reader = + IstreamReader::Emplace<std::istringstream>("abcdefghij"); + + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(3), count); + + status = reader.Skip(10, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(7), count); + + status = reader.Skip(1, &count); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); +} + +TEST_F(IstreamReaderTest, ReadAndSkip) { + std::array<std::uint8_t, 10> buffer = {}; + std::uint64_t count; + Status status; + + IstreamReader reader = + IstreamReader::Emplace<std::istringstream>("AaBbCcDdEe"); + + status = reader.Read(5, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(3), count); + + status = reader.Read(5, buffer.data() + 5, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + std::array<std::uint8_t, 10> expected = ArrayFromString("AaBbCEe\0\0\0"); + EXPECT_EQ(expected, buffer); +} + +TEST_F(IstreamReaderTest, Position) { + std::array<std::uint8_t, 10> buffer = {}; + std::uint64_t count; + Status status; + + IstreamReader reader = + IstreamReader::Emplace<std::istringstream>("AaBbCcDdEe"); + EXPECT_EQ(static_cast<std::uint64_t>(0), reader.Position()); + + status = reader.Read(5, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(5), count); + EXPECT_EQ(static_cast<std::uint64_t>(5), reader.Position()); + + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(3), count); + EXPECT_EQ(static_cast<std::uint64_t>(8), reader.Position()); + + status = reader.Read(5, buffer.data() + 5, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + EXPECT_EQ(static_cast<std::uint64_t>(10), reader.Position()); + + std::array<std::uint8_t, 10> expected = ArrayFromString("AaBbCEe\0\0\0"); + EXPECT_EQ(expected, buffer); +} + +} // namespace diff --git a/webm_parser/tests/limited_reader_test.cc b/webm_parser/tests/limited_reader_test.cc new file mode 100644 index 0000000..9064a45 --- /dev/null +++ b/webm_parser/tests/limited_reader_test.cc @@ -0,0 +1,250 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "test_utils/limited_reader.h" + +#include <array> +#include <cstdint> +#include <memory> + +#include "gtest/gtest.h" + +#include "webm/buffer_reader.h" +#include "webm/reader.h" +#include "webm/status.h" + +using webm::BufferReader; +using webm::LimitedReader; +using webm::Reader; +using webm::Status; + +namespace { + +class LimitedReaderTest : public testing::Test {}; + +TEST_F(LimitedReaderTest, UnlimitedRead) { + std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}}; + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(4), count); + + std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 3, 4}}; + EXPECT_EQ(expected_buffer, buffer); +} + +TEST_F(LimitedReaderTest, UnlimitedSkip) { + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + status = reader.Skip(4, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(4), count); +} + +TEST_F(LimitedReaderTest, Position) { + std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}}; + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + EXPECT_EQ(static_cast<std::uint64_t>(0), reader.Position()); + + status = reader.Read(2, buffer.data(), &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + status = reader.Skip(2, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + EXPECT_EQ(static_cast<std::uint64_t>(4), reader.Position()); + + std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 0, 0}}; + EXPECT_EQ(expected_buffer, buffer); +} + +TEST_F(LimitedReaderTest, LimitIndividualRead) { + std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}}; + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + reader.set_single_read_limit(1); + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + status = reader.Read(buffer.size() + 1, buffer.data() + 1, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + reader.set_single_read_limit(2); + status = reader.Read(buffer.size() - 2, buffer.data() + 2, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 3, 4}}; + EXPECT_EQ(expected_buffer, buffer); +} + +TEST_F(LimitedReaderTest, LimitIndividualSkip) { + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + reader.set_single_skip_limit(1); + status = reader.Skip(4, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + reader.set_single_skip_limit(2); + status = reader.Skip(2, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); +} + +TEST_F(LimitedReaderTest, LimitRepeatedRead) { + std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}}; + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + reader.set_total_read_limit(1); + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kWouldBlock, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); + + reader.set_total_read_limit(2); + status = reader.Read(buffer.size() - 1, buffer.data() + 1, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + reader.set_total_read_limit(1); + status = reader.Read(buffer.size() - 3, buffer.data() + 3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 3, 4}}; + EXPECT_EQ(expected_buffer, buffer); +} + +TEST_F(LimitedReaderTest, LimitRepeatedSkip) { + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + reader.set_total_skip_limit(1); + status = reader.Skip(4, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + status = reader.Skip(4, &count); + EXPECT_EQ(Status::kWouldBlock, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); + + reader.set_total_skip_limit(2); + status = reader.Skip(3, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + reader.set_total_skip_limit(1); + status = reader.Skip(1, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); +} + +TEST_F(LimitedReaderTest, LimitReadsAndSkips) { + std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}}; + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + + reader.set_total_read_skip_limit(1); + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + status = reader.Skip(buffer.size(), &count); + EXPECT_EQ(Status::kWouldBlock, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), count); + + reader.set_total_read_skip_limit(2); + status = reader.Skip(buffer.size() - 1, &count); + EXPECT_EQ(Status::kOkPartial, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(2), count); + + reader.set_total_read_skip_limit(1); + status = reader.Read(buffer.size() - 3, buffer.data() + 3, &count); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), count); + + std::array<std::uint8_t, 4> expected = {{1, 0, 0, 4}}; + EXPECT_EQ(expected, buffer); +} + +TEST_F(LimitedReaderTest, CustomStatusWhenBlocked) { + std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}}; + LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4}))); + std::uint64_t count; + Status status; + Status expected_status = Status(-123); + const std::uint64_t kZeroCount = 0; + + reader.set_single_read_limit(0); + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(Status::kWouldBlock, status.code); + EXPECT_EQ(kZeroCount, count); + + reader.set_return_status_when_blocked(expected_status); + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(expected_status.code, status.code); + EXPECT_EQ(kZeroCount, count); + + reader.set_single_read_limit(std::numeric_limits<std::size_t>::max()); + reader.set_total_read_limit(0); + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(expected_status.code, status.code); + EXPECT_EQ(kZeroCount, count); + + reader.set_total_read_limit(std::numeric_limits<std::size_t>::max()); + reader.set_single_skip_limit(0); + status = reader.Skip(buffer.size(), &count); + EXPECT_EQ(expected_status.code, status.code); + EXPECT_EQ(kZeroCount, count); + + reader.set_single_skip_limit(std::numeric_limits<std::uint64_t>::max()); + reader.set_total_skip_limit(0); + status = reader.Skip(buffer.size(), &count); + EXPECT_EQ(expected_status.code, status.code); + EXPECT_EQ(kZeroCount, count); + + reader.set_total_skip_limit(std::numeric_limits<std::uint64_t>::max()); + reader.set_total_read_skip_limit(0); + status = reader.Read(buffer.size(), buffer.data(), &count); + EXPECT_EQ(expected_status.code, status.code); + EXPECT_EQ(kZeroCount, count); + status = reader.Skip(buffer.size(), &count); + EXPECT_EQ(expected_status.code, status.code); + EXPECT_EQ(kZeroCount, count); + + std::array<std::uint8_t, 4> expected_buffer = {{0, 0, 0, 0}}; + EXPECT_EQ(expected_buffer, buffer); +} + +} // namespace diff --git a/webm_parser/tests/master_parser_test.cc b/webm_parser/tests/master_parser_test.cc new file mode 100644 index 0000000..5f36fd2 --- /dev/null +++ b/webm_parser/tests/master_parser_test.cc @@ -0,0 +1,382 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/master_parser.h" + +#include <cstdint> +#include <memory> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "src/byte_parser.h" +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/status.h" + +using testing::_; +using testing::DoAll; +using testing::InSequence; +using testing::NotNull; +using testing::Return; +using testing::SetArgPointee; + +using webm::Action; +using webm::BinaryParser; +using webm::ElementMetadata; +using webm::ElementParser; +using webm::ElementParserTest; +using webm::Id; +using webm::kUnknownElementSize; +using webm::LimitedReader; +using webm::MasterParser; +using webm::Status; + +namespace { + +// Simple helper method that just takes an Id and ElementParser* and returns +// them in a std::pair<Id, std::unique_ptr<ElementParser>>. Provided just for +// simplifying some statements. +std::pair<Id, std::unique_ptr<ElementParser>> ParserForId( + Id id, ElementParser* parser) { + return {id, std::unique_ptr<ElementParser>(parser)}; +} + +class MasterParserTest : public ElementParserTest<MasterParser> {}; + +// Errors parsing an ID should be returned to the caller. +TEST_F(MasterParserTest, BadId) { + SetReaderData({ + 0x00, // Invalid ID. + 0x80, // ID = 0x80 (unknown). + 0x80, // Size = 0. + }); + + EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0); + + ParseAndExpectResult(Status::kInvalidElementId); +} + +// Errors from a child parser's Init should be returned to the caller. +TEST_F(MasterParserTest, ChildInitFails) { + SetReaderData({ + 0xEC, // ID = 0xEC (Void). + 0xFF, // Size = unknown. + }); + + const ElementMetadata metadata = {Id::kVoid, 2, kUnknownElementSize, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + ParseAndExpectResult(Status::kInvalidElementSize); +} + +// Indefinite unknown children should result in an error. +TEST_F(MasterParserTest, IndefiniteUnknownChild) { + SetReaderData({ + 0x80, // ID = 0x80 (unknown). + 0xFF, // Size = unknown. + 0x00, 0x00, // Body. + }); + + EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0); + + ParseAndExpectResult(Status::kIndefiniteUnknownElement); +} + +// Child elements that overflow the master element's size should be detected. +TEST_F(MasterParserTest, ChildOverflow) { + SetReaderData({ + 0xEC, // ID = 0xEC (Void). + 0x82, // Size = 2. + 0x00, 0x00, // Body. + }); + + EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0); + EXPECT_CALL(callback_, OnVoid(_, _, _)).Times(0); + + ParseAndExpectResult(Status::kElementOverflow, reader_.size() - 1); +} + +// Child elements with an unknown size can't be naively checked to see if they +// overflow the master element's size. Make sure the overflow is still detected. +TEST_F(MasterParserTest, ChildOverflowWithUnknownSize) { + SetReaderData({ + 0xA1, // ID = 0xA1 (Block) (master). + 0xFF, // Size = unknown. + + 0xEC, // ID = 0xEC (Void) (child). + 0x81, // Size = 1. + 0x12, // Body. + }); + + { + InSequence dummy; + + ElementMetadata metadata = {Id::kBlock, 2, kUnknownElementSize, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + } + + ResetParser(ParserForId(Id::kBlock, new MasterParser)); + + ParseAndExpectResult(Status::kElementOverflow, 4); +} + +// An element with an unknown size should be terminated by its parents bounds. +TEST_F(MasterParserTest, ChildWithUnknownSizeBoundedByParentSize) { + SetReaderData({ + 0xA1, // ID = 0xA1 (Block) (master). + 0xFF, // Size = unknown. + + 0xEC, // ID = 0xEC (Void) (child). + 0x81, // Size = 1. + 0x12, // Body. + + 0x00, // Invalid ID. This should not be read. + }); + + { + InSequence dummy; + + ElementMetadata metadata = {Id::kBlock, 2, kUnknownElementSize, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kVoid, 2, 1, 2}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1); + } + + ResetParser(ParserForId(Id::kBlock, new MasterParser)); + + ParseAndVerify(reader_.size() - 1); +} + +// An empty master element is okay. +TEST_F(MasterParserTest, Empty) { + EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0); + + ParseAndVerify(); +} + +TEST_F(MasterParserTest, DefaultActionIsRead) { + SetReaderData({ + 0xEC, // ID = 0xEC (Void). + 0x80, // Size = 0. + }); + + { + InSequence dummy; + + const ElementMetadata metadata = {Id::kVoid, 2, 0, 0}; + + // This intentionally does not set the action and relies on the parser using + // a default action value of kRead. + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())) + .WillOnce(Return(Status(Status::kOkCompleted))); + EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1); + } + + ParseAndVerify(); +} + +// Unrecognized children should be skipped over. +TEST_F(MasterParserTest, UnknownChildren) { + SetReaderData({ + 0x40, 0x00, // ID = 0x4000 (unknown). + 0x80, // Size = 0. + + 0x80, // ID = 0x80 (unknown). + 0x40, 0x00, // Size = 0. + }); + + EXPECT_CALL(callback_, OnVoid(_, _, _)).Times(0); + { + InSequence dummy; + + ElementMetadata metadata = {static_cast<Id>(0x4000), 3, 0, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {static_cast<Id>(0x80), 3, 0, 3}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + } + + ParseAndVerify(); +} + +// A master element with unknown size is terminated by the first element that is +// not a valid child. +TEST_F(MasterParserTest, UnknownSize) { + SetReaderData({ + // Void elements may appear anywhere in a master element and should not + // terminate the parse for a master element with an unknown size. In other + // words, they're always valid children. + 0xEC, // ID = 0xEC (Void). + 0x81, // Size = 1. + 0x00, // Body. + + // This element marks the end for the parser since this is the first + // unrecognized element. The ID and size should be read (which the parser + // uses to determine the end has been reached), but nothing more. + 0x80, // ID = 0x80 (unknown). + 0x81, // Size = 1. + 0x12, // Body. + }); + + { + InSequence dummy; + + const ElementMetadata metadata = {Id::kVoid, 2, 1, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1); + } + + ParseAndVerify(kUnknownElementSize); + + EXPECT_EQ(static_cast<std::uint64_t>(5), reader_.Position()); +} + +// Consecutive elements with unknown size should parse without issues, despite +// the internal parsers having to read ahead into the next (non-child) element. +TEST_F(MasterParserTest, MultipleUnknownChildSize) { + SetReaderData({ + 0xA1, // ID = 0xA1 (Block) (master). + 0xFF, // Size = unknown. + + 0xEC, // ID = 0xEC (Void) (child). + 0x81, // Size = 1. + 0x12, // Body. + + 0xA1, // ID = 0xA1 (Block) (master). + 0xFF, // Size = unknown. + + 0xEC, // ID = 0xEC (Void) (child). + 0x81, // Size = 1. + 0x13, // Body. + }); + + { + InSequence dummy; + + ElementMetadata metadata = {Id::kBlock, 2, kUnknownElementSize, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kVoid, 2, 1, 2}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1); + + metadata = {Id::kBlock, 2, kUnknownElementSize, 5}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kVoid, 2, 1, 7}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1); + } + + ResetParser(ParserForId(Id::kBlock, new MasterParser)); + + ParseAndVerify(); +} + +// Reaching the end of the file while reading an element with unknown size +// should return Status::kOkCompleted instead of Status::kEndOfFile. +TEST_F(MasterParserTest, UnknownSizeToFileEnd) { + SetReaderData({ + 0xEC, // ID = 0xEC (Void). + 0x81, // Size = 1. + 0x00, // Body. + }); + + { + InSequence dummy; + + const ElementMetadata metadata = {Id::kVoid, 2, 1, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1); + } + + ParseAndVerify(); +} + +// Parsing one byte at a time is okay. +TEST_F(MasterParserTest, IncrementalParse) { + SetReaderData({ + 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). + 0x08, 0x00, 0x00, 0x00, 0x06, // Size = 6. + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // Body. + }); + + const ElementMetadata metadata = {Id::kEbml, 9, 6, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + BinaryParser* binary_parser = new BinaryParser; + ResetParser(ParserForId(Id::kEbml, binary_parser)); + + IncrementalParseAndVerify(); + + std::vector<std::uint8_t> expected = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; + EXPECT_EQ(expected, binary_parser->value()); +} + +// Alternating actions between skip and read is okay. The parser should remember +// the requested action between repeated calls to Feed. +TEST_F(MasterParserTest, IncrementalSkipThenReadThenSkip) { + SetReaderData({ + 0xA1, // ID = 0xA1 (Block) (master). + 0x83, // Size = 3. + + 0xEC, // ID = 0xEC (Void) (child). + 0x81, // Size = 1. + 0x12, // Body. + + 0xA1, // ID = 0xA1 (Block) (master). + 0x83, // Size = 3. + + 0xEC, // ID = 0xEC (Void) (child). + 0x81, // Size = 1. + 0x13, // Body. + + 0xA1, // ID = 0xA1 (Block) (master). + 0xFF, // Size = unknown. + + 0xEC, // ID = 0xEC (Void) (child). + 0x81, // Size = 1. + 0x14, // Body. + }); + + { + InSequence dummy; + + ElementMetadata metadata = {Id::kBlock, 2, 3, 0}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())) + .WillOnce(Return(Status(Status::kOkPartial))) + .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + metadata = {Id::kBlock, 2, 3, 5}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kVoid, 2, 1, 7}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + // Expect to get called twice because we'll cap the LimitedReader to 1-byte + // reads. The first attempt to read will fail because we'll have already + // reached the 1-byte max. + EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(2); + + metadata = {Id::kBlock, 2, kUnknownElementSize, 10}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + } + + ResetParser(ParserForId(Id::kBlock, new MasterParser)); + + IncrementalParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/master_value_parser_test.cc b/webm_parser/tests/master_value_parser_test.cc new file mode 100644 index 0000000..c9d5779 --- /dev/null +++ b/webm_parser/tests/master_value_parser_test.cc @@ -0,0 +1,366 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/master_value_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "src/int_parser.h" +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/status.h" + +using testing::_; +using testing::DoAll; +using testing::InSequence; +using testing::NotNull; +using testing::Return; +using testing::SetArgPointee; + +using webm::Action; +using webm::Callback; +using webm::Cluster; +using webm::ElementMetadata; +using webm::ElementParserTest; +using webm::Id; +using webm::MasterValueParser; +using webm::Status; +using webm::UnsignedIntParser; + +namespace { + +// An instance of MasterValueParser that can actually be used in the tests to +// parse Cluster structures. +class FakeClusterParser : public MasterValueParser<Cluster> { + public: + FakeClusterParser() + : MasterValueParser( + MakeChild<UnsignedIntParser>(Id::kTimecode, &Cluster::timecode), + MakeChild<UnsignedIntParser>(Id::kPrevSize, &Cluster::previous_size) + .UseAsStartEvent()) {} + + protected: + Status OnParseStarted(Callback* callback, Action* action) override { + return callback->OnClusterBegin(metadata(Id::kCluster), value(), action); + } + + Status OnParseCompleted(Callback* callback) override { + return callback->OnClusterEnd(metadata(Id::kCluster), value()); + } +}; + +class MasterValueParserTest + : public ElementParserTest<FakeClusterParser, Id::kCluster> {}; + +TEST_F(MasterValueParserTest, InvalidId) { + SetReaderData({ + 0x00, // Invalid ID. + }); + + ParseAndExpectResult(Status::kInvalidElementId); +} + +TEST_F(MasterValueParserTest, InvalidSize) { + SetReaderData({ + 0xE7, // ID = 0xE7 (Timecode). + 0xFF, // Size = unknown. + }); + + ParseAndExpectResult(Status::kInvalidElementSize); +} + +TEST_F(MasterValueParserTest, DefaultParse) { + { + InSequence dummy; + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, Cluster{}, NotNull())) + .Times(1); + + EXPECT_CALL(callback_, OnClusterEnd(metadata_, Cluster{})).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(MasterValueParserTest, DefaultValues) { + SetReaderData({ + 0xE7, // ID = 0xE7 (Timecode). + 0x80, // Size = 0. + + 0xAB, // ID = 0xAB (PrevSize). + 0x80, // Size = 0. + }); + + { + InSequence dummy; + + Cluster cluster{}; + cluster.timecode.Set(0, true); + + ElementMetadata child_metadata = {Id::kTimecode, 2, 0, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + child_metadata = {Id::kPrevSize, 2, 0, 2}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + cluster.previous_size.Set(0, true); + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(MasterValueParserTest, CustomValues) { + SetReaderData({ + 0xE7, // ID = 0xE7 (Timecode). + 0x82, // Size = 2. + 0x04, 0x00, // Body (value = 1024). + + 0xAB, // ID = 0xAB (PrevSize). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + }); + + { + InSequence dummy; + + Cluster cluster{}; + cluster.timecode.Set(1024, true); + + ElementMetadata child_metadata = {Id::kTimecode, 2, 2, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + child_metadata = {Id::kPrevSize, 3, 1, 4}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + cluster.previous_size.Set(1, true); + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(MasterValueParserTest, IncrementalParse) { + SetReaderData({ + 0xE7, // ID = 0xE7 (Timecode). + 0x40, 0x02, // Size = 2. + 0x04, 0x00, // Body (value = 1024). + + 0x40, 0x00, // ID = 0x4000 (Unknown). + 0x40, 0x02, // Size = 2. + 0x00, 0x00, // Body. + + 0xAB, // ID = 0xAB (PrevSize). + 0x40, 0x02, // Size = 2. + 0x00, 0x01, // Body (value = 1). + }); + + { + InSequence dummy; + + Cluster cluster{}; + cluster.timecode.Set(1024, true); + + ElementMetadata child_metadata = {Id::kTimecode, 3, 2, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + child_metadata = {static_cast<Id>(0x4000), 4, 2, 5}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + EXPECT_CALL(callback_, + OnUnknownElement(child_metadata, NotNull(), NotNull())) + .Times(3); + + child_metadata = {Id::kPrevSize, 3, 2, 11}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + cluster.previous_size.Set(1, true); + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + IncrementalParseAndVerify(); +} + +TEST_F(MasterValueParserTest, StartEventWithoutPreviousSize) { + SetReaderData({ + 0xE7, // ID = 0xE7 (Timecode). + 0x80, // Size = 0. + }); + + { + InSequence dummy; + + Cluster cluster{}; + + const ElementMetadata child_metadata = {Id::kTimecode, 2, 0, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + cluster.timecode.Set(0, true); + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(MasterValueParserTest, StartEventWithPreviousSize) { + SetReaderData({ + 0xAB, // ID = 0xAB (PrevSize). + 0x80, // Size = 0. + + 0xE7, // ID = 0xE7 (Timecode). + 0x80, // Size = 0. + }); + + { + InSequence dummy; + + Cluster cluster{}; + + ElementMetadata child_metadata = {Id::kPrevSize, 2, 0, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .Times(1); + + child_metadata = {Id::kTimecode, 2, 0, 2}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + cluster.previous_size.Set(0, true); + cluster.timecode.Set(0, true); + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(MasterValueParserTest, PartialStartThenRead) { + SetReaderData({ + 0xAB, // ID = 0xAB (PrevSize). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xE7, // ID = 0xE7 (Timecode). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + }); + + { + InSequence dummy; + + Cluster cluster{}; + + ElementMetadata child_metadata = {Id::kPrevSize, 2, 1, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .WillOnce(Return(Status(Status::kOkPartial))) + .WillOnce(DoAll(SetArgPointee<2>(Action::kRead), + Return(Status(Status::kOkCompleted)))); + + child_metadata = {Id::kTimecode, 2, 1, 3}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + cluster.previous_size.Set(1, true); + cluster.timecode.Set(1, true); + EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1); + } + + IncrementalParseAndVerify(); +} + +TEST_F(MasterValueParserTest, EmptySkip) { + const Cluster cluster{}; + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0); + EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0); + + ParseAndVerify(); + + EXPECT_EQ(cluster, parser_.value()); +} + +TEST_F(MasterValueParserTest, Skip) { + SetReaderData({ + 0xAB, // ID = 0xAB (PrevSize). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xE7, // ID = 0xE7 (Timecode). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + }); + + const Cluster cluster{}; + + { + InSequence dummy; + + const ElementMetadata child_metadata = {Id::kPrevSize, 2, 1, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0); + } + + ParseAndVerify(); + + EXPECT_EQ(cluster, parser_.value()); +} + +TEST_F(MasterValueParserTest, PartialStartThenSkip) { + SetReaderData({ + 0xAB, // ID = 0xAB (PrevSize). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xE7, // ID = 0xE7 (Timecode). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + }); + + const Cluster cluster{}; + + { + InSequence dummy; + + const ElementMetadata child_metadata = {Id::kPrevSize, 2, 1, 0}; + EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull())) + .WillOnce(Return(Status(Status::kOkPartial))) + .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0); + } + + IncrementalParseAndVerify(); + + EXPECT_EQ(cluster, parser_.value()); +} + +} // namespace diff --git a/webm_parser/tests/mastering_metadata_parser_test.cc b/webm_parser/tests/mastering_metadata_parser_test.cc new file mode 100644 index 0000000..cfbb264 --- /dev/null +++ b/webm_parser/tests/mastering_metadata_parser_test.cc @@ -0,0 +1,208 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/mastering_metadata_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::MasteringMetadata; +using webm::MasteringMetadataParser; + +namespace { + +class MasteringMetadataParserTest + : public ElementParserTest<MasteringMetadataParser, + Id::kMasteringMetadata> {}; + +TEST_F(MasteringMetadataParserTest, DefaultParse) { + ParseAndVerify(); + + const MasteringMetadata mastering_metadata = parser_.value(); + + EXPECT_FALSE(mastering_metadata.primary_r_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_x.value()); + + EXPECT_FALSE(mastering_metadata.primary_r_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_y.value()); + + EXPECT_FALSE(mastering_metadata.primary_g_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_x.value()); + + EXPECT_FALSE(mastering_metadata.primary_g_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_y.value()); + + EXPECT_FALSE(mastering_metadata.primary_b_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_x.value()); + + EXPECT_FALSE(mastering_metadata.primary_b_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_y.value()); + + EXPECT_FALSE(mastering_metadata.white_point_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_x.value()); + + EXPECT_FALSE(mastering_metadata.white_point_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_y.value()); + + EXPECT_FALSE(mastering_metadata.luminance_max.is_present()); + EXPECT_EQ(0, mastering_metadata.luminance_max.value()); + + EXPECT_FALSE(mastering_metadata.luminance_min.is_present()); + EXPECT_EQ(0, mastering_metadata.luminance_min.value()); +} + +TEST_F(MasteringMetadataParserTest, DefaultValues) { + SetReaderData({ + 0x55, 0xD1, // ID = 0x55D1 (PrimaryRChromaticityX). + 0x80, // Size = 0. + + 0x55, 0xD2, // ID = 0x55D2 (PrimaryRChromaticityY). + 0x80, // Size = 0. + + 0x55, 0xD3, // ID = 0x55D3 (PrimaryGChromaticityX). + 0x80, // Size = 0. + + 0x55, 0xD4, // ID = 0x55D4 (PrimaryGChromaticityY). + 0x80, // Size = 0. + + 0x55, 0xD5, // ID = 0x55D5 (PrimaryBChromaticityX). + 0x80, // Size = 0. + + 0x55, 0xD6, // ID = 0x55D6 (PrimaryBChromaticityY). + 0x80, // Size = 0. + + 0x55, 0xD7, // ID = 0x55D7 (WhitePointChromaticityX). + 0x80, // Size = 0. + + 0x55, 0xD8, // ID = 0x55D8 (WhitePointChromaticityY). + 0x80, // Size = 0. + + 0x55, 0xD9, // ID = 0x55D9 (LuminanceMax). + 0x80, // Size = 0. + + 0x55, 0xDA, // ID = 0x55DA (LuminanceMin). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const MasteringMetadata mastering_metadata = parser_.value(); + + EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.white_point_chromaticity_x.is_present()); + EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.white_point_chromaticity_y.is_present()); + EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.luminance_max.is_present()); + EXPECT_EQ(0, mastering_metadata.luminance_max.value()); + + EXPECT_TRUE(mastering_metadata.luminance_min.is_present()); + EXPECT_EQ(0, mastering_metadata.luminance_min.value()); +} + +TEST_F(MasteringMetadataParserTest, CustomValues) { + SetReaderData({ + 0x55, 0xD1, // ID = 0x55D1 (PrimaryRChromaticityX). + 0x84, // Size = 4. + 0x3E, 0x00, 0x00, 0x00, // Body (value = 0.125). + + 0x55, 0xD2, // ID = 0x55D2 (PrimaryRChromaticityY). + 0x84, // Size = 4. + 0x3E, 0x80, 0x00, 0x00, // Body (value = 0.25). + + 0x55, 0xD3, // ID = 0x55D3 (PrimaryGChromaticityX). + 0x84, // Size = 4. + 0x3E, 0xC0, 0x00, 0x00, // Body (value = 0.375). + + 0x55, 0xD4, // ID = 0x55D4 (PrimaryGChromaticityY). + 0x84, // Size = 4. + 0x3F, 0x00, 0x00, 0x00, // Body (value = 0.5). + + 0x55, 0xD5, // ID = 0x55D5 (PrimaryBChromaticityX). + 0x84, // Size = 4. + 0x3F, 0x20, 0x00, 0x00, // Body (value = 0.625). + + 0x55, 0xD6, // ID = 0x55D6 (PrimaryBChromaticityY). + 0x84, // Size = 4. + 0x3F, 0x40, 0x00, 0x00, // Body (value = 0.75). + + 0x55, 0xD7, // ID = 0x55D7 (WhitePointChromaticityX). + 0x84, // Size = 4. + 0x3F, 0x60, 0x00, 0x00, // Body (value = 0.875). + + 0x55, 0xD8, // ID = 0x55D8 (WhitePointChromaticityY). + 0x84, // Size = 4. + 0x3F, 0x80, 0x00, 0x00, // Body (value = 1). + + 0x55, 0xD9, // ID = 0x55D9 (LuminanceMax). + 0x84, // Size = 4. + 0x40, 0x00, 0x00, 0x00, // Body (value = 2). + + 0x55, 0xDA, // ID = 0x55DA (LuminanceMin). + 0x84, // Size = 4. + 0x40, 0x40, 0x00, 0x00, // Body (value = 3). + }); + + ParseAndVerify(); + + const MasteringMetadata mastering_metadata = parser_.value(); + + EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_x.is_present()); + EXPECT_EQ(0.125, mastering_metadata.primary_r_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_y.is_present()); + EXPECT_EQ(0.25, mastering_metadata.primary_r_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_x.is_present()); + EXPECT_EQ(0.375, mastering_metadata.primary_g_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_y.is_present()); + EXPECT_EQ(0.5, mastering_metadata.primary_g_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_x.is_present()); + EXPECT_EQ(0.625, mastering_metadata.primary_b_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_y.is_present()); + EXPECT_EQ(0.75, mastering_metadata.primary_b_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.white_point_chromaticity_x.is_present()); + EXPECT_EQ(0.875, mastering_metadata.white_point_chromaticity_x.value()); + + EXPECT_TRUE(mastering_metadata.white_point_chromaticity_y.is_present()); + EXPECT_EQ(1.0, mastering_metadata.white_point_chromaticity_y.value()); + + EXPECT_TRUE(mastering_metadata.luminance_max.is_present()); + EXPECT_EQ(2.0, mastering_metadata.luminance_max.value()); + + EXPECT_TRUE(mastering_metadata.luminance_min.is_present()); + EXPECT_EQ(3.0, mastering_metadata.luminance_min.value()); +} + +} // namespace diff --git a/webm_parser/tests/parser_utils_test.cc b/webm_parser/tests/parser_utils_test.cc new file mode 100644 index 0000000..69d3679 --- /dev/null +++ b/webm_parser/tests/parser_utils_test.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/parser_utils.h" + +#include <cstdint> +#include <limits> +#include <memory> + +#include "gtest/gtest.h" + +#include "test_utils/limited_reader.h" +#include "webm/buffer_reader.h" +#include "webm/reader.h" +#include "webm/status.h" + +using webm::BufferReader; +using webm::LimitedReader; +using webm::Reader; +using webm::Status; + +namespace { + +class ParserUtilsTest : public testing::Test {}; + +TEST_F(ParserUtilsTest, ReadByte) { + LimitedReader reader( + std::unique_ptr<Reader>(new BufferReader({0x12, 0x34, 0x56, 0x78}))); + + Status status; + std::uint8_t byte; + + status = ReadByte(&reader, &byte); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(0x12, byte); + + status = ReadByte(&reader, &byte); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(0x34, byte); + + status = ReadByte(&reader, &byte); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(0x56, byte); + + reader.set_total_read_limit(0); + status = ReadByte(&reader, &byte); + EXPECT_EQ(Status::kWouldBlock, status.code); + EXPECT_EQ(0x56, byte); + + reader.set_total_read_limit(std::numeric_limits<std::size_t>::max()); + status = ReadByte(&reader, &byte); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(0x78, byte); + + status = ReadByte(&reader, &byte); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(0x78, byte); +} + +TEST_F(ParserUtilsTest, AccumulateIntegerBytes) { + LimitedReader reader( + std::unique_ptr<Reader>(new BufferReader({0x12, 0x34, 0x56, 0x78}))); + + std::uint32_t integer = 0; + Status status; + std::uint64_t num_bytes_actually_read; + + reader.set_total_read_limit(1); + status = + AccumulateIntegerBytes(4, &reader, &integer, &num_bytes_actually_read); + EXPECT_EQ(Status::kWouldBlock, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), num_bytes_actually_read); + EXPECT_EQ(static_cast<std::uint32_t>(0x12), integer); + + reader.set_total_read_limit(std::numeric_limits<std::size_t>::max()); + status = + AccumulateIntegerBytes(3, &reader, &integer, &num_bytes_actually_read); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(3), num_bytes_actually_read); + EXPECT_EQ(static_cast<std::uint32_t>(0x12345678), integer); + + // Make sure calling with num_bytes_remaining == 0 is a no-op. + status = + AccumulateIntegerBytes(0, &reader, &integer, &num_bytes_actually_read); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), num_bytes_actually_read); + EXPECT_EQ(static_cast<std::uint32_t>(0x12345678), integer); + + status = + AccumulateIntegerBytes(4, &reader, &integer, &num_bytes_actually_read); + EXPECT_EQ(Status::kEndOfFile, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(0), num_bytes_actually_read); + EXPECT_EQ(static_cast<std::uint32_t>(0x12345678), integer); +} + +} // namespace diff --git a/webm_parser/tests/projection_parser_test.cc b/webm_parser/tests/projection_parser_test.cc new file mode 100644 index 0000000..747c4f5 --- /dev/null +++ b/webm_parser/tests/projection_parser_test.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/projection_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::Projection; +using webm::ProjectionParser; +using webm::ProjectionType; + +namespace { + +class ProjectionParserTest + : public ElementParserTest<ProjectionParser, Id::kProjection> {}; + +TEST_F(ProjectionParserTest, DefaultParse) { + ParseAndVerify(); + + const Projection projection = parser_.value(); + + EXPECT_FALSE(projection.type.is_present()); + EXPECT_EQ(ProjectionType::kRectangular, projection.type.value()); + + EXPECT_FALSE(projection.projection_private.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, projection.projection_private.value()); + + EXPECT_FALSE(projection.pose_yaw.is_present()); + EXPECT_EQ(0.0, projection.pose_yaw.value()); + + EXPECT_FALSE(projection.pose_pitch.is_present()); + EXPECT_EQ(0.0, projection.pose_pitch.value()); + + EXPECT_FALSE(projection.pose_roll.is_present()); + EXPECT_EQ(0.0, projection.pose_roll.value()); +} + +TEST_F(ProjectionParserTest, DefaultValues) { + SetReaderData({ + 0x76, 0x71, // ID = 0x7671 (ProjectionType). + 0x80, // Size = 0. + + 0x76, 0x72, // ID = 0x7672 (ProjectionPrivate). + 0x80, // Size = 0. + + 0x76, 0x73, // ID = 0x7673 (ProjectionPoseYaw). + 0x80, // Size = 0. + + 0x76, 0x74, // ID = 0x7674 (ProjectionPosePitch). + 0x80, // Size = 0. + + 0x76, 0x75, // ID = 0x7675 (ProjectionPoseRoll). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const Projection projection = parser_.value(); + + EXPECT_TRUE(projection.type.is_present()); + EXPECT_EQ(ProjectionType::kRectangular, projection.type.value()); + + EXPECT_TRUE(projection.projection_private.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, projection.projection_private.value()); + + EXPECT_TRUE(projection.pose_yaw.is_present()); + EXPECT_EQ(0.0, projection.pose_yaw.value()); + + EXPECT_TRUE(projection.pose_pitch.is_present()); + EXPECT_EQ(0.0, projection.pose_pitch.value()); + + EXPECT_TRUE(projection.pose_roll.is_present()); + EXPECT_EQ(0.0, projection.pose_roll.value()); +} + +TEST_F(ProjectionParserTest, CustomValues) { + SetReaderData({ + 0x76, 0x71, // ID = 0x7671 (ProjectionType). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = equirectangular). + + 0x76, 0x72, // ID = 0x7672 (ProjectionPrivate). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x00, // Body. + + 0x76, 0x73, // ID = 0x7673 (ProjectionPoseYaw). + 0x10, 0x00, 0x00, 0x04, // Size = 4. + 0x3f, 0x80, 0x00, 0x00, // Body (value = 1.0). + + 0x76, 0x74, // ID = 0x7674 (ProjectionPosePitch). + 0x10, 0x00, 0x00, 0x04, // Size = 4. + 0x40, 0x00, 0x00, 0x00, // Body (value = 2.0). + + 0x76, 0x75, // ID = 0x7675 (ProjectionPoseRoll). + 0x10, 0x00, 0x00, 0x04, // Size = 4. + 0x40, 0x80, 0x00, 0x00, // Body (value = 4.0). + }); + + ParseAndVerify(); + + const Projection projection = parser_.value(); + + EXPECT_TRUE(projection.type.is_present()); + EXPECT_EQ(ProjectionType::kEquirectangular, projection.type.value()); + + EXPECT_TRUE(projection.projection_private.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{0x00}, + projection.projection_private.value()); + + EXPECT_TRUE(projection.pose_yaw.is_present()); + EXPECT_EQ(1.0, projection.pose_yaw.value()); + + EXPECT_TRUE(projection.pose_pitch.is_present()); + EXPECT_EQ(2.0, projection.pose_pitch.value()); + + EXPECT_TRUE(projection.pose_roll.is_present()); + EXPECT_EQ(4.0, projection.pose_roll.value()); +} + +TEST_F(ProjectionParserTest, MeshProjection) { + SetReaderData({ + 0x76, 0x71, // ID = 0x7671 (ProjectionType). + 0x81, // Size = 1. + 0x03, // Body (value = mesh). + }); + + ParseAndVerify(); + + const Projection projection = parser_.value(); + + EXPECT_TRUE(projection.type.is_present()); + EXPECT_EQ(ProjectionType::kMesh, projection.type.value()); +} + +} // namespace diff --git a/webm_parser/tests/recursive_parser_test.cc b/webm_parser/tests/recursive_parser_test.cc new file mode 100644 index 0000000..8965a3e --- /dev/null +++ b/webm_parser/tests/recursive_parser_test.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/recursive_parser.h" + +#include <cstdint> + +#include "gtest/gtest.h" + +#include "src/byte_parser.h" +#include "src/element_parser.h" +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/status.h" + +using webm::Callback; +using webm::ElementMetadata; +using webm::ElementParser; +using webm::ElementParserTest; +using webm::Reader; +using webm::RecursiveParser; +using webm::Status; +using webm::StringParser; + +namespace { + +class FailParser : public ElementParser { + public: + explicit FailParser(std::size_t /* max_recursion_depth */) { ADD_FAILURE(); } + + Status Init(const ElementMetadata& /* metadata */, + std::uint64_t /* max_size */) override { + ADD_FAILURE(); + return Status(Status::kInvalidElementSize); + } + + Status Feed(Callback* /* callback */, Reader* /* reader */, + std::uint64_t* num_bytes_read) override { + ADD_FAILURE(); + *num_bytes_read = 0; + return Status(Status::kInvalidElementSize); + } + + int value() const { + ADD_FAILURE(); + return 0; + } + + int* mutable_value() { + ADD_FAILURE(); + return nullptr; + } +}; + +class StringParserWrapper : public StringParser { + public: + explicit StringParserWrapper(std::size_t max_recursion_depth) { + EXPECT_EQ(max_recursion_depth, 24); + } +}; + +class RecursiveFailParserTest + : public ElementParserTest<RecursiveParser<FailParser>> {}; + +TEST_F(RecursiveFailParserTest, NoConstruction) { + RecursiveParser<FailParser> parser; +} + +class RecursiveStringParserTest + : public ElementParserTest<RecursiveParser<StringParserWrapper>> {}; + +TEST_F(RecursiveStringParserTest, ParsesOkay) { + ParseAndVerify(); + EXPECT_EQ("", parser_.value()); + + SetReaderData({0x48, 0x69}); // "Hi". + ParseAndVerify(); + EXPECT_EQ("Hi", parser_.value()); +} + +TEST_F(RecursiveStringParserTest, ExceedMaxRecursionDepth) { + ResetParser(0); + TestInit(0, Status::kExceededRecursionDepthLimit); +} + +} // namespace diff --git a/webm_parser/tests/seek_head_parser_test.cc b/webm_parser/tests/seek_head_parser_test.cc new file mode 100644 index 0000000..4fb6ce5 --- /dev/null +++ b/webm_parser/tests/seek_head_parser_test.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/seek_head_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/buffer_reader.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::SeekHeadParser; + +namespace { + +class SeekHeadParserTest + : public ElementParserTest<SeekHeadParser, Id::kSeekHead> {}; + +TEST_F(SeekHeadParserTest, DefaultValues) { + ParseAndVerify(); + + SetReaderData({ + 0x4D, 0xBB, // ID = 0x4DBB (Seek). + 0x80, // Size = 0. + }); + ParseAndVerify(); +} + +TEST_F(SeekHeadParserTest, RepeatedValues) { + SetReaderData({ + 0x4D, 0xBB, // ID = 0x4DBB (Seek). + 0x84, // Size = 4. + + 0x53, 0xAC, // ID = 0x53AC (SeekPosition). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x4D, 0xBB, // ID = 0x4DBB (Seek). + 0x84, // Size = 4. + + 0x53, 0xAC, // ID = 0x53AC (SeekPosition). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/seek_parser_test.cc b/webm_parser/tests/seek_parser_test.cc new file mode 100644 index 0000000..e829140 --- /dev/null +++ b/webm_parser/tests/seek_parser_test.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/seek_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::Seek; +using webm::SeekParser; + +namespace { + +class SeekParserTest : public ElementParserTest<SeekParser, Id::kSeek> {}; + +TEST_F(SeekParserTest, DefaultParse) { + EXPECT_CALL(callback_, OnSeek(metadata_, Seek{})).Times(1); + + ParseAndVerify(); +} + +TEST_F(SeekParserTest, DefaultValues) { + SetReaderData({ + // A SeekID element with a length of 0 isn't valid, so we don't test it. + + 0x53, 0xAC, // ID = 0x53AC (SeekPosition). + 0x80, // Size = 0. + }); + + Seek seek; + seek.position.Set(0, true); + + EXPECT_CALL(callback_, OnSeek(metadata_, seek)).Times(1); + + ParseAndVerify(); +} + +TEST_F(SeekParserTest, CustomValues) { + SetReaderData({ + 0x53, 0xAB, // ID = 0x53AB (SeekID). + 0x81, // Size = 1. + 0x01, // Body. + + 0x53, 0xAC, // ID = 0x53AC (SeekPosition). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + Seek seek; + seek.id.Set(static_cast<Id>(0x01), true); + seek.position.Set(2, true); + + EXPECT_CALL(callback_, OnSeek(metadata_, seek)).Times(1); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/segment_parser_test.cc b/webm_parser/tests/segment_parser_test.cc new file mode 100644 index 0000000..d1a444b --- /dev/null +++ b/webm_parser/tests/segment_parser_test.cc @@ -0,0 +1,369 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/segment_parser.h" + +#include <cstdint> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" +#include "webm/status.h" + +using testing::_; +using testing::DoAll; +using testing::InSequence; +using testing::NotNull; +using testing::Return; +using testing::SetArgPointee; + +using webm::Action; +using webm::Ancestory; +using webm::ElementMetadata; +using webm::ElementParserTest; +using webm::Id; +using webm::SegmentParser; +using webm::Status; + +namespace { + +class SegmentParserTest + : public ElementParserTest<SegmentParser, Id::kSegment> {}; + +TEST_F(SegmentParserTest, EmptyParse) { + { + InSequence dummy; + + EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())).Times(1); + EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(SegmentParserTest, DefaultActionIsRead) { + { + InSequence dummy; + + // This intentionally does not set the action and relies on the parser using + // a default action value of kRead. + EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())) + .WillOnce(Return(Status(Status::kOkCompleted))); + EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(SegmentParserTest, EmptyParseWithDelayedStart) { + { + InSequence dummy; + + EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())) + .WillOnce(Return(Status(Status::kOkPartial))) + .WillOnce(DoAll(SetArgPointee<1>(Action::kRead), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1); + } + + IncrementalParseAndVerify(); +} + +TEST_F(SegmentParserTest, DefaultValues) { + SetReaderData({ + 0x11, 0x4D, 0x9B, 0x74, // ID = 0x114D9B74 (SeekHead). + 0x80, // Size = 0. + + 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info). + 0x80, // Size = 0. + + 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster). + 0x80, // Size = 0. + + 0x16, 0x54, 0xAE, 0x6B, // ID = 0x1654AE6B (Tracks). + 0x80, // Size = 0. + + 0x1C, 0x53, 0xBB, 0x6B, // ID = 0x1C53BB6B (Cues). + 0x80, // Size = 0. + + 0x10, 0x43, 0xA7, 0x70, // ID = 0x1043A770 (Chapters). + 0x80, // Size = 0. + + 0x12, 0x54, 0xC3, 0x67, // ID = 0x1254C367 (Tags). + 0x80, // Size = 0. + }); + + { + InSequence dummy; + + EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())).Times(1); + + ElementMetadata metadata = {Id::kInfo, 5, 0, 5}; + EXPECT_CALL(callback_, OnInfo(metadata, _)).Times(1); + + metadata = {Id::kCluster, 5, 0, 10}; + EXPECT_CALL(callback_, OnClusterBegin(metadata, _, NotNull())).Times(1); + EXPECT_CALL(callback_, OnClusterEnd(metadata, _)).Times(1); + + EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(SegmentParserTest, RepeatedValues) { + SetReaderData({ + // Mutliple SeekHead elements. + 0x11, 0x4D, 0x9B, 0x74, // ID = 0x114D9B74 (SeekHead). + 0x8D, // Size = 13. + + 0x4D, 0xBB, // ID = 0x4DBB (Seek). + 0x10, 0x00, 0x00, 0x07, // Size = 7. + + 0x53, 0xAC, // ID = 0x53AC (SeekPosition). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x11, 0x4D, 0x9B, 0x74, // ID = 0x114D9B74 (SeekHead). + 0x8D, // Size = 13. + + 0x4D, 0xBB, // ID = 0x4DBB (Seek). + 0x10, 0x00, 0x00, 0x07, // Size = 7. + + 0x53, 0xAC, // ID = 0x53AC (SeekPosition). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + // Multiple Info elements. + 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info). + 0x88, // Size = 8. + + 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info). + 0x88, // Size = 8. + + 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + // Multiple Cluster elements. + 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster). + 0x86, // Size = 6. + + 0xE7, // ID = 0xE7 (Timecode). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster). + 0x86, // Size = 6. + + 0xE7, // ID = 0xE7 (Timecode). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + // Multiple Tracks elements. + 0x16, 0x54, 0xAE, 0x6B, // ID = 0x1654AE6B (Tracks). + 0x8B, // Size = 11. + + 0xAE, // ID = 0xAE (TrackEntry). + 0x10, 0x00, 0x00, 0x06, // Size = 6. + + 0xD7, // ID = 0xD7 (TrackNumber). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x16, 0x54, 0xAE, 0x6B, // ID = 0x1654AE6B (Tracks). + 0x8B, // Size = 11. + + 0xAE, // ID = 0xAE (TrackEntry). + 0x10, 0x00, 0x00, 0x06, // Size = 6. + + 0xD7, // ID = 0xD7 (TrackNumber). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + // Single Cues element. + 0x1C, 0x53, 0xBB, 0x6B, // ID = 0x1C53BB6B (Cues). + 0x8B, // Size = 11. + + 0xBB, // ID = 0xBB (CuePoint). + 0x10, 0x00, 0x00, 0x06, // Size = 6. + + 0xB3, // ID = 0xB3 (CueTime). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + // Single Chapters element. + 0x10, 0x43, 0xA7, 0x70, // ID = 0x1043A770 (Chapters). + 0x8D, // Size = 13. + + 0x45, 0xB9, // ID = 0x45B9 (EditionEntry). + 0x10, 0x00, 0x00, 0x07, // Size = 7. + + 0x45, 0xBC, // ID = 0x45BC (EditionUID). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + // Multiple Tags elements. + 0x12, 0x54, 0xC3, 0x67, // ID = 0x1254C367 (Tags). + 0x93, // Size = 19. + + 0x73, 0x73, // ID = 0x7373 (Tag). + 0x10, 0x00, 0x00, 0x0D, // Size = 13. + + 0x63, 0xC0, // ID = 0x63C0 (Targets). + 0x10, 0x00, 0x00, 0x07, // Size = 7. + + 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x12, 0x54, 0xC3, 0x67, // ID = 0x1254C367 (Tags). + 0x93, // Size = 19. + + 0x73, 0x73, // ID = 0x7373 (Tag). + 0x10, 0x00, 0x00, 0x0D, // Size = 13. + + 0x63, 0xC0, // ID = 0x63C0 (Targets). + 0x10, 0x00, 0x00, 0x07, // Size = 7. + + 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + }); + + { + InSequence dummy; + + EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnSeek(_, _)).Times(2); + + EXPECT_CALL(callback_, OnInfo(_, _)).Times(2); + + EXPECT_CALL(callback_, OnClusterBegin(_, _, NotNull())).Times(1); + EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(1); + + EXPECT_CALL(callback_, OnClusterBegin(_, _, NotNull())).Times(1); + EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(1); + + EXPECT_CALL(callback_, OnTrackEntry(_, _)).Times(2); + + EXPECT_CALL(callback_, OnCuePoint(_, _)).Times(1); + + EXPECT_CALL(callback_, OnEditionEntry(_, _)).Times(1); + + EXPECT_CALL(callback_, OnTag(_, _)).Times(2); + + EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1); + } + + ParseAndVerify(); +} + +TEST_F(SegmentParserTest, Skip) { + SetReaderData({ + 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster). + 0x80, // Size = 0. + }); + + { + InSequence dummy; + + EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())) + .WillOnce(Return(Status(Status::kOkPartial))) + .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip), + Return(Status(Status::kOkCompleted)))); + + EXPECT_CALL(callback_, OnClusterBegin(_, _, _)).Times(0); + EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0); + + EXPECT_CALL(callback_, OnSegmentEnd(_)).Times(0); + } + + IncrementalParseAndVerify(); +} + +TEST_F(SegmentParserTest, Seek) { + SetReaderData({ + // We start the reader at the beginning of a FlagInterlaced element + // (skipping its ID and size, as they're passed into InitAfterSeek). + // FlagInterlaced, StereoMode, and AlphaMode are all part of the Video + // element, with the ancestory: Segment -> Tracks -> TrackEntry -> Video. + 0x01, // Body (value = 1). + + 0x53, 0xB8, // ID = 0x53B8 (StereoMode). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0x53, 0xC0, // ID = 0x53C0 (AlphaMode). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x03, // Body (value = 3). + + // Single Cues element. + 0x1C, 0x53, 0xBB, 0x6B, // ID = 0x1C53BB6B (Cues). + 0x8B, // Size = 11. + + 0xBB, // ID = 0xBB (CuePoint). + 0x10, 0x00, 0x00, 0x06, // Size = 6. + + 0xB3, // ID = 0xB3 (CueTime). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + }); + + const ElementMetadata flag_interlaced_metadata = {Id::kFlagInterlaced, 0, 1, + 0}; + + EXPECT_CALL(callback_, OnSegmentBegin(_, _)).Times(0); + { + InSequence dummy; + + EXPECT_CALL(callback_, OnElementBegin(flag_interlaced_metadata, NotNull())) + .Times(1); + + ElementMetadata metadata = {Id::kStereoMode, 6, 1, 1}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kAlphaMode, 6, 1, 8}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnTrackEntry(_, _)).Times(1); + + metadata = {Id::kCues, 5, 11, 15}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kCuePoint, 5, 6, 20}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kCueTime, 5, 1, 25}; + EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1); + + EXPECT_CALL(callback_, OnCuePoint(_, _)).Times(1); + + EXPECT_CALL(callback_, OnSegmentEnd(_)).Times(1); + } + + Ancestory ancestory; + ASSERT_TRUE(Ancestory::ById(flag_interlaced_metadata.id, &ancestory)); + ancestory = ancestory.next(); // Skip the Segment ancestor. + + parser_.InitAfterSeek(ancestory, flag_interlaced_metadata); + + std::uint64_t num_bytes_read = 0; + const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(reader_.size(), num_bytes_read); +} + +} // namespace diff --git a/webm_parser/tests/simple_tag_parser_test.cc b/webm_parser/tests/simple_tag_parser_test.cc new file mode 100644 index 0000000..f67191c --- /dev/null +++ b/webm_parser/tests/simple_tag_parser_test.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/simple_tag_parser.h" + +#include <cstdint> + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" +#include "webm/status.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::SimpleTag; +using webm::SimpleTagParser; +using webm::Status; + +namespace { + +class SimpleTagParserTest + : public ElementParserTest<SimpleTagParser, Id::kSimpleTag> {}; + +TEST_F(SimpleTagParserTest, DefaultParse) { + ParseAndVerify(); + + const SimpleTag simple_tag = parser_.value(); + + EXPECT_FALSE(simple_tag.name.is_present()); + EXPECT_EQ("", simple_tag.name.value()); + + EXPECT_FALSE(simple_tag.language.is_present()); + EXPECT_EQ("und", simple_tag.language.value()); + + EXPECT_FALSE(simple_tag.is_default.is_present()); + EXPECT_EQ(true, simple_tag.is_default.value()); + + EXPECT_FALSE(simple_tag.string.is_present()); + EXPECT_EQ("", simple_tag.string.value()); + + EXPECT_FALSE(simple_tag.binary.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, simple_tag.binary.value()); + + EXPECT_EQ(static_cast<std::size_t>(0), simple_tag.tags.size()); +} + +TEST_F(SimpleTagParserTest, DefaultValues) { + SetReaderData({ + 0x45, 0xA3, // ID = 0x45A3 (TagName). + 0x80, // Size = 0. + + 0x44, 0x7A, // ID = 0x447A (TagLanguage). + 0x80, // Size = 0. + + 0x44, 0x84, // ID = 0x4484 (TagDefault). + 0x80, // Size = 0. + + 0x44, 0x87, // ID = 0x4487 (TagString). + 0x80, // Size = 0. + + 0x44, 0x85, // ID = 0x4485 (TagBinary). + 0x80, // Size = 0. + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const SimpleTag simple_tag = parser_.value(); + + EXPECT_TRUE(simple_tag.name.is_present()); + EXPECT_EQ("", simple_tag.name.value()); + + EXPECT_TRUE(simple_tag.language.is_present()); + EXPECT_EQ("und", simple_tag.language.value()); + + EXPECT_TRUE(simple_tag.is_default.is_present()); + EXPECT_EQ(true, simple_tag.is_default.value()); + + EXPECT_TRUE(simple_tag.string.is_present()); + EXPECT_EQ("", simple_tag.string.value()); + + EXPECT_TRUE(simple_tag.binary.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{}, simple_tag.binary.value()); + + ASSERT_EQ(static_cast<std::size_t>(1), simple_tag.tags.size()); + EXPECT_TRUE(simple_tag.tags[0].is_present()); + EXPECT_EQ(SimpleTag{}, simple_tag.tags[0].value()); +} + +TEST_F(SimpleTagParserTest, CustomValues) { + SetReaderData({ + 0x45, 0xA3, // ID = 0x45A3 (TagName). + 0x81, // Size = 1. + 0x61, // Body (value = "a"). + + 0x44, 0x7A, // ID = 0x447A (TagLanguage). + 0x81, // Size = 1. + 0x62, // Body (value = "b"). + + 0x44, 0x84, // ID = 0x4484 (TagDefault). + 0x81, // Size = 1. + 0x00, // Body (value = 0). + + 0x44, 0x87, // ID = 0x4487 (TagString). + 0x81, // Size = 1. + 0x63, // Body (value = "c"). + + 0x44, 0x85, // ID = 0x4485 (TagBinary). + 0x81, // Size = 1. + 0x01, // Body. + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x99, // Size = 25. + + 0x44, 0x87, // ID = 0x4487 (TagString). + 0x81, // Size = 1. + 0x64, // Body (value = "d"). + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x8B, // Size = 11. + + 0x44, 0x87, // ID = 0x4487 (TagString). + 0x81, // Size = 1. + 0x65, // Body (value = "e"). + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x84, // Size = 4. + + 0x44, 0x87, // ID = 0x4487 (TagString). + 0x81, // Size = 1. + 0x66, // Body (value = "f"). + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x84, // Size = 4. + + 0x44, 0x87, // ID = 0x4487 (TagString). + 0x81, // Size = 1. + 0x67, // Body (value = "g"). + }); + + ParseAndVerify(); + + const SimpleTag simple_tag = parser_.value(); + + EXPECT_TRUE(simple_tag.name.is_present()); + EXPECT_EQ("a", simple_tag.name.value()); + + EXPECT_TRUE(simple_tag.language.is_present()); + EXPECT_EQ("b", simple_tag.language.value()); + + EXPECT_TRUE(simple_tag.is_default.is_present()); + EXPECT_EQ(false, simple_tag.is_default.value()); + + EXPECT_TRUE(simple_tag.string.is_present()); + EXPECT_EQ("c", simple_tag.string.value()); + + EXPECT_TRUE(simple_tag.binary.is_present()); + EXPECT_EQ(std::vector<std::uint8_t>{0x01}, simple_tag.binary.value()); + + SimpleTag expected; + expected.string.Set("d", true); + + SimpleTag temp{}; + + temp.string.Set("e", true); + expected.tags.emplace_back(temp, true); + + temp.string.Set("f", true); + expected.tags[0].mutable_value()->tags.emplace_back(temp, true); + + temp.string.Set("g", true); + expected.tags.emplace_back(temp, true); + + ASSERT_EQ(static_cast<std::size_t>(1), simple_tag.tags.size()); + EXPECT_TRUE(simple_tag.tags[0].is_present()); + EXPECT_EQ(expected, simple_tag.tags[0].value()); +} + +TEST_F(SimpleTagParserTest, ExceedMaxRecursionDepth) { + ResetParser(1); + + SetReaderData({ + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x80, // Size = 0. + }); + ParseAndVerify(); + + SetReaderData({ + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x83, // Size = 3. + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x80, // Size = 0. + }); + ParseAndExpectResult(Status::kExceededRecursionDepthLimit); +} + +} // namespace diff --git a/webm_parser/tests/size_parser_test.cc b/webm_parser/tests/size_parser_test.cc new file mode 100644 index 0000000..2cb7f53 --- /dev/null +++ b/webm_parser/tests/size_parser_test.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/size_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/parser_test.h" +#include "webm/status.h" + +using webm::ParserTest; +using webm::SizeParser; +using webm::Status; + +namespace { + +class SizeParserTest : public ParserTest<SizeParser> {}; + +TEST_F(SizeParserTest, InvalidSize) { + SetReaderData({0x00}); + ParseAndExpectResult(Status::kInvalidElementSize); +} + +TEST_F(SizeParserTest, EarlyEndOfFile) { + SetReaderData({0x01}); + ParseAndExpectResult(Status::kEndOfFile); +} + +TEST_F(SizeParserTest, ValidSize) { + SetReaderData({0x80}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0), parser_.size()); + + ResetParser(); + SetReaderData({0x01, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0x123456789ABCDE), parser_.size()); +} + +TEST_F(SizeParserTest, UnknownSize) { + SetReaderData({0xFF}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0xFFFFFFFFFFFFFFFF), parser_.size()); + + ResetParser(); + SetReaderData({0x1F, 0xFF, 0xFF, 0xFF}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0xFFFFFFFFFFFFFFFF), parser_.size()); +} + +TEST_F(SizeParserTest, IncrementalParse) { + SetReaderData({0x11, 0x23, 0x45, 0x67}); + IncrementalParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0x01234567), parser_.size()); +} + +} // namespace diff --git a/webm_parser/tests/skip_parser_test.cc b/webm_parser/tests/skip_parser_test.cc new file mode 100644 index 0000000..8c728be --- /dev/null +++ b/webm_parser/tests/skip_parser_test.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/skip_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/status.h" + +using webm::ElementParserTest; +using webm::kUnknownElementSize; +using webm::SkipParser; +using webm::Status; + +namespace { + +class SkipParserTest : public ElementParserTest<SkipParser> {}; + +TEST_F(SkipParserTest, InvalidSize) { + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(SkipParserTest, Skip) { + ParseAndVerify(); + + SetReaderData({0x00, 0x01, 0x02, 0x04}); + ParseAndVerify(); +} + +TEST_F(SkipParserTest, IncrementalSkip) { + SetReaderData({0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10}); + + IncrementalParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/slices_parser_test.cc b/webm_parser/tests/slices_parser_test.cc new file mode 100644 index 0000000..44abba6 --- /dev/null +++ b/webm_parser/tests/slices_parser_test.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/slices_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::Slices; +using webm::SlicesParser; +using webm::TimeSlice; + +namespace { + +class SlicesParserTest : public ElementParserTest<SlicesParser, Id::kSlices> {}; + +TEST_F(SlicesParserTest, DefaultParse) { + ParseAndVerify(); + + const Slices slices = parser_.value(); + + EXPECT_EQ(static_cast<std::size_t>(0), slices.slices.size()); +} + +TEST_F(SlicesParserTest, DefaultValues) { + SetReaderData({ + 0xE8, // ID = 0xE8 (TimeSlice). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const Slices slices = parser_.value(); + + ASSERT_EQ(static_cast<std::size_t>(1), slices.slices.size()); + EXPECT_TRUE(slices.slices[0].is_present()); + EXPECT_EQ(TimeSlice{}, slices.slices[0].value()); +} + +TEST_F(SlicesParserTest, CustomValues) { + SetReaderData({ + 0xE8, // ID = 0xE8 (TimeSlice). + 0x83, // Size = 3. + + 0xCC, // ID = 0xCC (LaceNumber). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xE8, // ID = 0xE8 (TimeSlice). + 0x83, // Size = 3. + + 0xCC, // ID = 0xCC (LaceNumber). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); + + const Slices slices = parser_.value(); + + TimeSlice expected; + + ASSERT_EQ(static_cast<std::size_t>(2), slices.slices.size()); + expected.lace_number.Set(1, true); + EXPECT_TRUE(slices.slices[0].is_present()); + EXPECT_EQ(expected, slices.slices[0].value()); + expected.lace_number.Set(2, true); + EXPECT_TRUE(slices.slices[1].is_present()); + EXPECT_EQ(expected, slices.slices[1].value()); +} + +} // namespace diff --git a/webm_parser/tests/tag_parser_test.cc b/webm_parser/tests/tag_parser_test.cc new file mode 100644 index 0000000..579c4b8 --- /dev/null +++ b/webm_parser/tests/tag_parser_test.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/tag_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::SimpleTag; +using webm::Tag; +using webm::TagParser; +using webm::Targets; + +namespace { + +class TagParserTest : public ElementParserTest<TagParser, Id::kTag> {}; + +TEST_F(TagParserTest, DefaultParse) { + EXPECT_CALL(callback_, OnTag(metadata_, Tag{})).Times(1); + + ParseAndVerify(); +} + +TEST_F(TagParserTest, DefaultValues) { + SetReaderData({ + 0x63, 0xC0, // ID = 0x63C0 (Targets). + 0x80, // Size = 0. + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x80, // Size = 0. + }); + + Tag tag; + tag.targets.Set({}, true); + tag.tags.emplace_back(); + tag.tags[0].Set({}, true); + + EXPECT_CALL(callback_, OnTag(metadata_, tag)).Times(1); + + ParseAndVerify(); +} + +TEST_F(TagParserTest, CustomValues) { + SetReaderData({ + 0x63, 0xC0, // ID = 0x63C0 (Targets). + 0x84, // Size = 4. + + 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue). + 0x81, // Size = 1. + 0x00, // Body (value = 0). + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x84, // Size = 4. + + 0x45, 0xA3, // ID = 0x45A3 (TagName). + 0x81, // Size = 1. + 0x61, // Body (value = "a"). + + 0x67, 0xC8, // ID = 0x67C8 (SimpleTag). + 0x84, // Size = 4. + + 0x44, 0x7A, // ID = 0x447A (TagLanguage). + 0x81, // Size = 1. + 0x62, // Body (value = "b"). + }); + + Tag tag; + Targets targets; + targets.type_value.Set(0, true); + tag.targets.Set(targets, true); + SimpleTag simple_tag; + simple_tag.name.Set("a", true); + tag.tags.emplace_back(simple_tag, true); + simple_tag = {}; + simple_tag.language.Set("b", true); + tag.tags.emplace_back(simple_tag, true); + + EXPECT_CALL(callback_, OnTag(metadata_, tag)).Times(1); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/tags_parser_test.cc b/webm_parser/tests/tags_parser_test.cc new file mode 100644 index 0000000..8b45f77 --- /dev/null +++ b/webm_parser/tests/tags_parser_test.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/tags_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/buffer_reader.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::TagsParser; + +namespace { + +class TagsParserTest : public ElementParserTest<TagsParser, Id::kTags> {}; + +// TODO(mjbshaw): validate results via Callback. + +TEST_F(TagsParserTest, DefaultValues) { + ParseAndVerify(); + + SetReaderData({ + 0x73, 0x73, // ID = 0x7373 (Tag). + 0x80, // Size = 0. + }); + ParseAndVerify(); +} + +TEST_F(TagsParserTest, RepeatedValues) { + SetReaderData({ + 0x73, 0x73, // ID = 0x7373 (Tag). + 0x87, // Size = 7. + + 0x63, 0xC0, // ID = 0x63C0 (Targets). + 0x84, // Size = 4. + + 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x73, 0x73, // ID = 0x7373 (Tag). + 0x87, // Size = 7. + + 0x63, 0xC0, // ID = 0x63C0 (Targets). + 0x84, // Size = 4. + + 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/targets_parser_test.cc b/webm_parser/tests/targets_parser_test.cc new file mode 100644 index 0000000..d424ecf --- /dev/null +++ b/webm_parser/tests/targets_parser_test.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/targets_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::Targets; +using webm::TargetsParser; + +namespace { + +class TargetsParserTest + : public ElementParserTest<TargetsParser, Id::kTargets> {}; + +TEST_F(TargetsParserTest, DefaultParse) { + ParseAndVerify(); + + const Targets targets = parser_.value(); + + EXPECT_FALSE(targets.type_value.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(50), targets.type_value.value()); + + EXPECT_FALSE(targets.type.is_present()); + EXPECT_EQ("", targets.type.value()); + + EXPECT_EQ(static_cast<std::size_t>(0), targets.track_uids.size()); +} + +TEST_F(TargetsParserTest, DefaultValues) { + SetReaderData({ + 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue). + 0x80, // Size = 0. + + 0x63, 0xCA, // ID = 0x63CA (TargetType). + 0x80, // Size = 0. + + 0x63, 0xC5, // ID = 0x63C5 (TagTrackUID). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const Targets targets = parser_.value(); + + EXPECT_TRUE(targets.type_value.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(50), targets.type_value.value()); + + EXPECT_TRUE(targets.type.is_present()); + EXPECT_EQ("", targets.type.value()); + + ASSERT_EQ(static_cast<std::size_t>(1), targets.track_uids.size()); + EXPECT_TRUE(targets.track_uids[0].is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), targets.track_uids[0].value()); +} + +TEST_F(TargetsParserTest, CustomValues) { + SetReaderData({ + 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue). + 0x81, // Size = 1. + 0x00, // Body (value = 0). + + 0x63, 0xCA, // ID = 0x63CA (TargetType). + 0x82, // Size = 2. + 0x48, 0x69, // Body (value = "Hi"). + + 0x63, 0xC5, // ID = 0x63C5 (TagTrackUID). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0x63, 0xC5, // ID = 0x63C5 (TagTrackUID). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); + + const Targets targets = parser_.value(); + + EXPECT_TRUE(targets.type_value.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), targets.type_value.value()); + + EXPECT_TRUE(targets.type.is_present()); + EXPECT_EQ("Hi", targets.type.value()); + + ASSERT_EQ(static_cast<std::size_t>(2), targets.track_uids.size()); + EXPECT_TRUE(targets.track_uids[0].is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), targets.track_uids[0].value()); + EXPECT_TRUE(targets.track_uids[1].is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), targets.track_uids[1].value()); +} + +} // namespace diff --git a/webm_parser/tests/time_slice_parser_test.cc b/webm_parser/tests/time_slice_parser_test.cc new file mode 100644 index 0000000..e9bd145 --- /dev/null +++ b/webm_parser/tests/time_slice_parser_test.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/time_slice_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::TimeSlice; +using webm::TimeSliceParser; + +namespace { + +class TimeSliceParserTest + : public ElementParserTest<TimeSliceParser, Id::kTimeSlice> {}; + +TEST_F(TimeSliceParserTest, DefaultParse) { + ParseAndVerify(); + + const TimeSlice time_slice = parser_.value(); + + EXPECT_FALSE(time_slice.lace_number.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), time_slice.lace_number.value()); +} + +TEST_F(TimeSliceParserTest, DefaultValues) { + SetReaderData({ + 0xCC, // ID = 0xCC (LaceNumber). + 0x80, // Size = 0. + }); + + ParseAndVerify(); + + const TimeSlice time_slice = parser_.value(); + + EXPECT_TRUE(time_slice.lace_number.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), time_slice.lace_number.value()); +} + +TEST_F(TimeSliceParserTest, CustomValues) { + SetReaderData({ + 0xCC, // ID = 0xCC (LaceNumber). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + }); + + ParseAndVerify(); + + const TimeSlice time_slice = parser_.value(); + + EXPECT_TRUE(time_slice.lace_number.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), time_slice.lace_number.value()); +} + +} // namespace diff --git a/webm_parser/tests/track_entry_parser_test.cc b/webm_parser/tests/track_entry_parser_test.cc new file mode 100644 index 0000000..70d06aa --- /dev/null +++ b/webm_parser/tests/track_entry_parser_test.cc @@ -0,0 +1,245 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/track_entry_parser.h" + +#include <cstdint> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::Audio; +using webm::ContentEncoding; +using webm::ContentEncodings; +using webm::DisplayUnit; +using webm::ElementParserTest; +using webm::Id; +using webm::TrackEntry; +using webm::TrackEntryParser; +using webm::TrackType; +using webm::Video; + +namespace { + +class TrackEntryParserTest + : public ElementParserTest<TrackEntryParser, Id::kTrackEntry> {}; + +TEST_F(TrackEntryParserTest, DefaultParse) { + EXPECT_CALL(callback_, OnTrackEntry(metadata_, TrackEntry{})).Times(1); + + ParseAndVerify(); +} + +TEST_F(TrackEntryParserTest, DefaultValues) { + SetReaderData({ + 0xD7, // ID = 0xD7 (TrackNumber). + 0x20, 0x00, 0x00, // Size = 0. + + 0x73, 0xC5, // ID = 0x73C5 (TrackUID). + 0x20, 0x00, 0x00, // Size = 0. + + 0x83, // ID = 0x83 (TrackType). + 0x20, 0x00, 0x00, // Size = 0. + + 0xB9, // ID = 0xB9 (FlagEnabled). + 0x20, 0x00, 0x00, // Size = 0. + + 0x88, // ID = 0x88 (FlagDefault). + 0x20, 0x00, 0x00, // Size = 0. + + 0x55, 0xAA, // ID = 0x55AA (FlagForced). + 0x20, 0x00, 0x00, // Size = 0. + + 0x9C, // ID = 0x9C (FlagLacing). + 0x20, 0x00, 0x00, // Size = 0. + + 0x23, 0xE3, 0x83, // ID = 0x23E383 (DefaultDuration). + 0x80, // Size = 0. + + 0x53, 0x6E, // ID = 0x536E (Name). + 0x20, 0x00, 0x00, // Size = 0. + + 0x22, 0xB5, 0x9C, // ID = 0x22B59C (Language). + 0x80, // Size = 0. + + 0x86, // ID = 0x86 (CodecID). + 0x20, 0x00, 0x00, // Size = 0. + + 0x63, 0xA2, // ID = 0x63A2 (CodecPrivate). + 0x20, 0x00, 0x00, // Size = 0. + + 0x25, 0x86, 0x88, // ID = 0x258688 (CodecName). + 0x80, // Size = 0. + + 0x56, 0xAA, // ID = 0x56AA (CodecDelay). + 0x20, 0x00, 0x00, // Size = 0. + + 0x56, 0xBB, // ID = 0x56BB (SeekPreRoll). + 0x20, 0x00, 0x00, // Size = 0. + + 0xE0, // ID = 0xE0 (Video). + 0x20, 0x00, 0x00, // Size = 0. + + 0xE1, // ID = 0xE1 (Audio). + 0x20, 0x00, 0x00, // Size = 0. + + 0x6D, 0x80, // ID = 0x6D80 (ContentEncodings). + 0x20, 0x00, 0x00, // Size = 0. + }); + + TrackEntry track_entry; + track_entry.track_number.Set(0, true); + track_entry.track_uid.Set(0, true); + track_entry.track_type.Set(TrackType{}, true); + track_entry.is_enabled.Set(true, true); + track_entry.is_default.Set(true, true); + track_entry.is_forced.Set(false, true); + track_entry.uses_lacing.Set(true, true); + track_entry.default_duration.Set(0, true); + track_entry.name.Set("", true); + track_entry.language.Set("eng", true); + track_entry.codec_id.Set("", true); + track_entry.codec_private.Set(std::vector<std::uint8_t>{}, true); + track_entry.codec_name.Set("", true); + track_entry.codec_delay.Set(0, true); + track_entry.seek_pre_roll.Set(0, true); + track_entry.video.Set(Video{}, true); + track_entry.audio.Set(Audio{}, true); + track_entry.content_encodings.Set(ContentEncodings{}, true); + + EXPECT_CALL(callback_, OnTrackEntry(metadata_, track_entry)).Times(1); + + ParseAndVerify(); +} + +TEST_F(TrackEntryParserTest, CustomValues) { + SetReaderData({ + 0xD7, // ID = 0xD7 (TrackNumber). + 0x20, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x73, 0xC5, // ID = 0x73C5 (TrackUID). + 0x20, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0x83, // ID = 0x83 (TrackType). + 0x20, 0x00, 0x01, // Size = 1. + 0x03, // Body (value = complex). + + 0xB9, // ID = 0xB9 (FlagEnabled). + 0x20, 0x00, 0x01, // Size = 1. + 0x00, // Body (value = 0). + + 0x88, // ID = 0x88 (FlagDefault). + 0x20, 0x00, 0x01, // Size = 1. + 0x00, // Body (value = 0). + + 0x55, 0xAA, // ID = 0x55AA (FlagForced). + 0x20, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x9C, // ID = 0x9C (FlagLacing). + 0x20, 0x00, 0x01, // Size = 1. + 0x00, // Body (value = 0). + + 0x23, 0xE3, 0x83, // ID = 0x23E383 (DefaultDuration). + 0x81, // Size = 1. + 0x04, // Body (value = 4). + + 0x53, 0x6E, // ID = 0x536E (Name). + 0x20, 0x00, 0x01, // Size = 1. + 0x41, // Body (value = "A"). + + 0x22, 0xB5, 0x9C, // ID = 0x22B59C (Language). + 0x81, // Size = 1. + 0x42, // Body (value = "B"). + + 0x86, // ID = 0x86 (CodecID). + 0x20, 0x00, 0x01, // Size = 1. + 0x43, // Body (value = "C"). + + 0x63, 0xA2, // ID = 0x63A2 (CodecPrivate). + 0x20, 0x00, 0x01, // Size = 1. + 0x00, // Body. + + 0x25, 0x86, 0x88, // ID = 0x258688 (CodecName). + 0x81, // Size = 1. + 0x44, // Body (value = "D"). + + 0x56, 0xAA, // ID = 0x56AA (CodecDelay). + 0x20, 0x00, 0x01, // Size = 1. + 0x05, // Body (value = 5). + + 0x56, 0xBB, // ID = 0x56BB (SeekPreRoll). + 0x20, 0x00, 0x01, // Size = 1. + 0x06, // Body (value = 6). + + 0xE0, // ID = 0xE0 (Video). + 0x20, 0x00, 0x06, // Size = 6. + + 0x54, 0xB2, // ID = 0x54B2 (DisplayUnit). + 0x20, 0x00, 0x01, // Size = 1. + 0x03, // Body (value = display aspect ratio). + + 0xE1, // ID = 0xE1 (Audio). + 0x20, 0x00, 0x05, // Size = 5. + + 0x9F, // ID = 0x9F (Channels). + 0x20, 0x00, 0x01, // Size = 1. + 0x08, // Body (value = 8). + + 0x6D, 0x80, // ID = 0x6D80 (ContentEncodings). + 0x20, 0x00, 0x0B, // Size = 11. + + 0x62, 0x40, // ID = 0x6240 (ContentEncoding). + 0x20, 0x00, 0x06, // Size = 6. + + 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder). + 0x20, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + }); + + TrackEntry track_entry; + track_entry.track_number.Set(1, true); + track_entry.track_uid.Set(2, true); + track_entry.track_type.Set(TrackType::kComplex, true); + track_entry.is_enabled.Set(false, true); + track_entry.is_default.Set(false, true); + track_entry.is_forced.Set(true, true); + track_entry.uses_lacing.Set(false, true); + track_entry.default_duration.Set(4, true); + track_entry.name.Set("A", true); + track_entry.language.Set("B", true); + track_entry.codec_id.Set("C", true); + track_entry.codec_private.Set(std::vector<std::uint8_t>{0x00}, true); + track_entry.codec_name.Set("D", true); + track_entry.codec_delay.Set(5, true); + track_entry.seek_pre_roll.Set(6, true); + + Video expected_video; + expected_video.display_unit.Set(DisplayUnit::kDisplayAspectRatio, true); + track_entry.video.Set(expected_video, true); + + Audio expected_audio; + expected_audio.channels.Set(8, true); + track_entry.audio.Set(expected_audio, true); + + ContentEncoding expected_encoding; + expected_encoding.order.Set(1, true); + ContentEncodings expected_encodings; + expected_encodings.encodings.emplace_back(expected_encoding, true); + track_entry.content_encodings.Set(expected_encodings, true); + + EXPECT_CALL(callback_, OnTrackEntry(metadata_, track_entry)).Times(1); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/tracks_parser_test.cc b/webm_parser/tests/tracks_parser_test.cc new file mode 100644 index 0000000..1d83af0 --- /dev/null +++ b/webm_parser/tests/tracks_parser_test.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/tracks_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/buffer_reader.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::TracksParser; + +namespace { + +class TracksParserTest : public ElementParserTest<TracksParser, Id::kTracks> {}; + +// TODO(mjbshaw): validate results via Callback. + +TEST_F(TracksParserTest, DefaultValues) { + ParseAndVerify(); + + SetReaderData({ + 0xAE, // ID = 0xAE (TrackEntry). + 0x80, // Size = 0. + }); + ParseAndVerify(); +} + +TEST_F(TracksParserTest, RepeatedValues) { + SetReaderData({ + 0xAE, // ID = 0xAE (TrackEntry). + 0x83, // Size = 3. + + 0xD7, // ID = 0xD7 (TrackNumber). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xAE, // ID = 0xAE (TrackEntry). + 0x83, // Size = 3. + + 0xD7, // ID = 0xD7 (TrackNumber). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/unknown_parser_test.cc b/webm_parser/tests/unknown_parser_test.cc new file mode 100644 index 0000000..c10b64d --- /dev/null +++ b/webm_parser/tests/unknown_parser_test.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/unknown_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/status.h" + +using testing::NotNull; + +using webm::ElementParserTest; +using webm::kUnknownElementSize; +using webm::Status; +using webm::UnknownParser; + +namespace { + +class UnknownParserTest : public ElementParserTest<UnknownParser> {}; + +TEST_F(UnknownParserTest, InvalidSize) { + TestInit(kUnknownElementSize, Status::kIndefiniteUnknownElement); +} + +TEST_F(UnknownParserTest, Empty) { + EXPECT_CALL(callback_, OnUnknownElement(metadata_, NotNull(), NotNull())) + .Times(1); + + ParseAndVerify(); +} + +TEST_F(UnknownParserTest, Valid) { + SetReaderData({0x00, 0x01, 0x02, 0x04}); + + EXPECT_CALL(callback_, OnUnknownElement(metadata_, NotNull(), NotNull())) + .Times(1); + + ParseAndVerify(); +} + +TEST_F(UnknownParserTest, IncrementalSkip) { + SetReaderData({0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10}); + + EXPECT_CALL(callback_, OnUnknownElement(metadata_, NotNull(), NotNull())) + .Times(8); + + IncrementalParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/var_int_parser_test.cc b/webm_parser/tests/var_int_parser_test.cc new file mode 100644 index 0000000..50e5915 --- /dev/null +++ b/webm_parser/tests/var_int_parser_test.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/var_int_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/parser_test.h" +#include "webm/status.h" + +using webm::ParserTest; +using webm::Status; +using webm::VarIntParser; + +namespace { + +class VarIntParserTest : public ParserTest<VarIntParser> {}; + +TEST_F(VarIntParserTest, NoMarkerBit) { + SetReaderData({0x00}); + ParseAndExpectResult(Status::kInvalidElementValue); +} + +TEST_F(VarIntParserTest, EarlyEndOfFile) { + SetReaderData({0x01}); + ParseAndExpectResult(Status::kEndOfFile); +} + +TEST_F(VarIntParserTest, ValidValue) { + SetReaderData({0x80}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0), parser_.value()); + EXPECT_EQ(1, parser_.encoded_length()); + + ResetParser(); + SetReaderData({0x01, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE}); + ParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0x123456789ABCDE), parser_.value()); + EXPECT_EQ(8, parser_.encoded_length()); +} + +TEST_F(VarIntParserTest, IncrementalParse) { + SetReaderData({0x11, 0x23, 0x45, 0x67}); + IncrementalParseAndVerify(); + EXPECT_EQ(static_cast<std::uint64_t>(0x01234567), parser_.value()); + EXPECT_EQ(4, parser_.encoded_length()); +} + +} // namespace diff --git a/webm_parser/tests/video_parser_test.cc b/webm_parser/tests/video_parser_test.cc new file mode 100644 index 0000000..96c7e49 --- /dev/null +++ b/webm_parser/tests/video_parser_test.cc @@ -0,0 +1,446 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/video_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" + +using webm::AspectRatioType; +using webm::Colour; +using webm::DisplayUnit; +using webm::ElementParserTest; +using webm::FlagInterlaced; +using webm::Id; +using webm::Projection; +using webm::ProjectionType; +using webm::StereoMode; +using webm::Video; +using webm::VideoParser; + +namespace { + +class VideoParserTest : public ElementParserTest<VideoParser, Id::kVideo> {}; + +TEST_F(VideoParserTest, DefaultParse) { + ParseAndVerify(); + + const Video video = parser_.value(); + + EXPECT_FALSE(video.interlaced.is_present()); + EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value()); + + EXPECT_FALSE(video.stereo_mode.is_present()); + EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value()); + + EXPECT_FALSE(video.alpha_mode.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value()); + + EXPECT_FALSE(video.pixel_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_width.value()); + + EXPECT_FALSE(video.pixel_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_height.value()); + + EXPECT_FALSE(video.pixel_crop_bottom.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value()); + + EXPECT_FALSE(video.pixel_crop_top.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value()); + + EXPECT_FALSE(video.pixel_crop_left.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value()); + + EXPECT_FALSE(video.pixel_crop_right.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value()); + + EXPECT_FALSE(video.display_width.is_present()); + EXPECT_EQ(video.pixel_width.value(), video.display_width.value()); + + EXPECT_FALSE(video.display_height.is_present()); + EXPECT_EQ(video.pixel_height.value(), video.display_height.value()); + + EXPECT_FALSE(video.display_unit.is_present()); + EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value()); + + EXPECT_FALSE(video.aspect_ratio_type.is_present()); + EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value()); + + EXPECT_FALSE(video.frame_rate.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.frame_rate.value()); + + EXPECT_FALSE(video.colour.is_present()); + EXPECT_EQ(Colour{}, video.colour.value()); + + EXPECT_FALSE(video.projection.is_present()); + EXPECT_EQ(Projection{}, video.projection.value()); +} + +TEST_F(VideoParserTest, DefaultValues) { + SetReaderData({ + 0x9A, // ID = 0x9A (FlagInterlaced). + 0x20, 0x00, 0x00, // Size = 0. + + 0x53, 0xB8, // ID = 0x53B8 (StereoMode). + 0x20, 0x00, 0x00, // Size = 0. + + 0x53, 0xC0, // ID = 0x53C0 (AlphaMode). + 0x20, 0x00, 0x00, // Size = 0. + + 0xB0, // ID = 0xB0 (PixelWidth). + 0x20, 0x00, 0x00, // Size = 0. + + 0xBA, // ID = 0xBA (PixelHeight). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xAA, // ID = 0x54AA (PixelCropBottom). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xBB, // ID = 0x54BB (PixelCropTop). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xCC, // ID = 0x54CC (PixelCropLeft). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xDD, // ID = 0x54DD (PixelCropRight). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xB0, // ID = 0x54B0 (DisplayWidth). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xBA, // ID = 0x54BA (DisplayHeight). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xB2, // ID = 0x54B2 (DisplayUnit). + 0x20, 0x00, 0x00, // Size = 0. + + 0x54, 0xB3, // ID = 0x54B3 (AspectRatioType). + 0x20, 0x00, 0x00, // Size = 0. + + 0x23, 0x83, 0xE3, // ID = 0x2383E3 (FrameRate). + 0x80, // Size = 0. + + 0x55, 0xB0, // ID = 0x55B0 (Colour). + 0x20, 0x00, 0x00, // Size = 0. + + 0x76, 0x70, // ID = 0x7670 (Projection). + 0x20, 0x00, 0x00, // Size = 0. + }); + + ParseAndVerify(); + + const Video video = parser_.value(); + + EXPECT_TRUE(video.interlaced.is_present()); + EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value()); + + EXPECT_TRUE(video.stereo_mode.is_present()); + EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value()); + + EXPECT_TRUE(video.alpha_mode.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value()); + + EXPECT_TRUE(video.pixel_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_width.value()); + + EXPECT_TRUE(video.pixel_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_height.value()); + + EXPECT_TRUE(video.pixel_crop_bottom.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value()); + + EXPECT_TRUE(video.pixel_crop_top.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value()); + + EXPECT_TRUE(video.pixel_crop_left.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value()); + + EXPECT_TRUE(video.pixel_crop_right.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value()); + + EXPECT_TRUE(video.display_width.is_present()); + EXPECT_EQ(video.pixel_width.value(), video.display_width.value()); + + EXPECT_TRUE(video.display_height.is_present()); + EXPECT_EQ(video.pixel_height.value(), video.display_height.value()); + + EXPECT_TRUE(video.display_unit.is_present()); + EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value()); + + EXPECT_TRUE(video.aspect_ratio_type.is_present()); + EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value()); + + EXPECT_TRUE(video.frame_rate.is_present()); + EXPECT_EQ(0, video.frame_rate.value()); + + EXPECT_TRUE(video.colour.is_present()); + EXPECT_EQ(Colour{}, video.colour.value()); + + EXPECT_TRUE(video.projection.is_present()); + EXPECT_EQ(Projection{}, video.projection.value()); +} + +TEST_F(VideoParserTest, CustomValues) { + SetReaderData({ + 0x9A, // ID = 0x9A (FlagInterlaced). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = progressive). + + 0x53, 0xB8, // ID = 0x53B8 (StereoMode). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = top-bottom (right eye first)). + + 0x53, 0xC0, // ID = 0x53C0 (AlphaMode). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x03, // Body (value = 3). + + 0xB0, // ID = 0xB0 (PixelWidth). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x04, // Body (value = 4). + + 0xBA, // ID = 0xBA (PixelHeight). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x05, // Body (value = 5). + + 0x54, 0xAA, // ID = 0x54AA (PixelCropBottom). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x06, // Body (value = 6). + + 0x54, 0xBB, // ID = 0x54BB (PixelCropTop). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x07, // Body (value = 7). + + 0x54, 0xCC, // ID = 0x54CC (PixelCropLeft). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x08, // Body (value = 8). + + 0x54, 0xDD, // ID = 0x54DD (PixelCropRight). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x09, // Body (value = 9). + + 0x54, 0xB0, // ID = 0x54B0 (DisplayWidth). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x0A, // Body (value = 10). + + 0x54, 0xBA, // ID = 0x54BA (DisplayHeight). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x0B, // Body (value = 11). + + 0x54, 0xB2, // ID = 0x54B2 (DisplayUnit). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = inches). + + 0x54, 0xB3, // ID = 0x54B3 (AspectRatioType). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = keep aspect ratio). + + 0x23, 0x83, 0xE3, // ID = 0x2383E3 (FrameRate). + 0x84, // Size = 4. + 0x40, 0x0F, 0x1B, 0xBD, // Body (value = 2.2360680103302001953125f). + + 0x55, 0xB0, // ID = 0x55B0 (Colour). + 0x10, 0x00, 0x00, 0x07, // Size = 7. + + 0x55, 0xB2, // ID = 0x55B2 (BitsPerChannel). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0x76, 0x70, // ID = 0x7670 (Projection). + 0x10, 0x00, 0x00, 0x07, // Size = 7. + + 0x76, 0x71, // ID = 0x7671 (ProjectionType). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = cube map). + }); + + ParseAndVerify(); + + const Video video = parser_.value(); + + EXPECT_TRUE(video.interlaced.is_present()); + EXPECT_EQ(FlagInterlaced::kProgressive, video.interlaced.value()); + + EXPECT_TRUE(video.stereo_mode.is_present()); + EXPECT_EQ(StereoMode::kTopBottomRightFirst, video.stereo_mode.value()); + + EXPECT_TRUE(video.alpha_mode.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(3), video.alpha_mode.value()); + + EXPECT_TRUE(video.pixel_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(4), video.pixel_width.value()); + + EXPECT_TRUE(video.pixel_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(5), video.pixel_height.value()); + + EXPECT_TRUE(video.pixel_crop_bottom.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(6), video.pixel_crop_bottom.value()); + + EXPECT_TRUE(video.pixel_crop_top.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(7), video.pixel_crop_top.value()); + + EXPECT_TRUE(video.pixel_crop_left.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(8), video.pixel_crop_left.value()); + + EXPECT_TRUE(video.pixel_crop_right.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(9), video.pixel_crop_right.value()); + + EXPECT_TRUE(video.display_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(10), video.display_width.value()); + + EXPECT_TRUE(video.display_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(11), video.display_height.value()); + + EXPECT_TRUE(video.display_unit.is_present()); + EXPECT_EQ(DisplayUnit::kInches, video.display_unit.value()); + + EXPECT_TRUE(video.aspect_ratio_type.is_present()); + EXPECT_EQ(AspectRatioType::kKeep, video.aspect_ratio_type.value()); + + EXPECT_TRUE(video.frame_rate.is_present()); + EXPECT_EQ(2.2360680103302001953125, video.frame_rate.value()); + + EXPECT_TRUE(video.colour.is_present()); + EXPECT_TRUE(video.colour.value().bits_per_channel.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), + video.colour.value().bits_per_channel.value()); + + EXPECT_TRUE(video.projection.is_present()); + EXPECT_TRUE(video.projection.value().type.is_present()); + EXPECT_EQ(ProjectionType::kCubeMap, video.projection.value().type.value()); +} + +TEST_F(VideoParserTest, AbsentDisplaySize) { + SetReaderData({ + 0xB0, // ID = 0xB0 (PixelWidth). + 0x81, // Size = 1. + 0x01, // Body (value = 1). + + 0xBA, // ID = 0xBA (PixelHeight). + 0x81, // Size = 1. + 0x02, // Body (value = 2). + }); + + ParseAndVerify(); + + const Video video = parser_.value(); + + EXPECT_FALSE(video.interlaced.is_present()); + EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value()); + + EXPECT_FALSE(video.stereo_mode.is_present()); + EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value()); + + EXPECT_FALSE(video.alpha_mode.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value()); + + EXPECT_TRUE(video.pixel_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), video.pixel_width.value()); + + EXPECT_TRUE(video.pixel_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), video.pixel_height.value()); + + EXPECT_FALSE(video.pixel_crop_bottom.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value()); + + EXPECT_FALSE(video.pixel_crop_top.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value()); + + EXPECT_FALSE(video.pixel_crop_left.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value()); + + EXPECT_FALSE(video.pixel_crop_right.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value()); + + EXPECT_FALSE(video.display_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), video.display_width.value()); + + EXPECT_FALSE(video.display_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), video.display_height.value()); + + EXPECT_FALSE(video.display_unit.is_present()); + EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value()); + + EXPECT_FALSE(video.aspect_ratio_type.is_present()); + EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value()); + + EXPECT_FALSE(video.frame_rate.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.frame_rate.value()); + + EXPECT_FALSE(video.colour.is_present()); + EXPECT_EQ(Colour{}, video.colour.value()); +} + +TEST_F(VideoParserTest, DefaultDisplaySize) { + SetReaderData({ + 0xB0, // ID = 0xB0 (PixelWidth). + 0x40, 0x01, // Size = 1. + 0x01, // Body (value = 1). + + 0xBA, // ID = 0xBA (PixelHeight). + 0x40, 0x01, // Size = 1. + 0x02, // Body (value = 2). + + 0x54, 0xB0, // ID = 0x54B0 (DisplayWidth). + 0x40, 0x00, // Size = 0. + + 0x54, 0xBA, // ID = 0x54BA (DisplayHeight). + 0x40, 0x00, // Size = 0. + }); + + ParseAndVerify(); + + const Video video = parser_.value(); + + EXPECT_FALSE(video.interlaced.is_present()); + EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value()); + + EXPECT_FALSE(video.stereo_mode.is_present()); + EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value()); + + EXPECT_FALSE(video.alpha_mode.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value()); + + EXPECT_TRUE(video.pixel_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), video.pixel_width.value()); + + EXPECT_TRUE(video.pixel_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), video.pixel_height.value()); + + EXPECT_FALSE(video.pixel_crop_bottom.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value()); + + EXPECT_FALSE(video.pixel_crop_top.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value()); + + EXPECT_FALSE(video.pixel_crop_left.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value()); + + EXPECT_FALSE(video.pixel_crop_right.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value()); + + EXPECT_TRUE(video.display_width.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(1), video.display_width.value()); + + EXPECT_TRUE(video.display_height.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(2), video.display_height.value()); + + EXPECT_FALSE(video.display_unit.is_present()); + EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value()); + + EXPECT_FALSE(video.aspect_ratio_type.is_present()); + EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value()); + + EXPECT_FALSE(video.frame_rate.is_present()); + EXPECT_EQ(static_cast<std::uint64_t>(0), video.frame_rate.value()); + + EXPECT_FALSE(video.colour.is_present()); + EXPECT_EQ(Colour{}, video.colour.value()); +} + +} // namespace diff --git a/webm_parser/tests/virtual_block_parser_test.cc b/webm_parser/tests/virtual_block_parser_test.cc new file mode 100644 index 0000000..97ef741 --- /dev/null +++ b/webm_parser/tests/virtual_block_parser_test.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/virtual_block_parser.h" + +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/id.h" +#include "webm/status.h" + +using webm::ElementParserTest; +using webm::Id; +using webm::kUnknownElementSize; +using webm::Status; +using webm::VirtualBlock; +using webm::VirtualBlockParser; + +namespace { + +class VirtualBlockParserTest + : public ElementParserTest<VirtualBlockParser, Id::kBlockVirtual> {}; + +TEST_F(VirtualBlockParserTest, InvalidSize) { + TestInit(3, Status::kInvalidElementSize); + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(VirtualBlockParserTest, InvalidBlock) { + SetReaderData({ + 0x40, 0x01, // Track number = 1. + 0x00, 0x00, // Timecode = 0. + 0x00, // Flags. + }); + + // Initialize with 1 byte short. + ParseAndExpectResult(Status::kInvalidElementValue, reader_.size() - 1); +} + +TEST_F(VirtualBlockParserTest, ValidBlock) { + SetReaderData({ + 0x81, // Track number = 1. + 0x12, 0x34, // Timecode = 4660. + 0x00, // Flags. + }); + + ParseAndVerify(); + + const VirtualBlock virtual_block = parser_.value(); + + EXPECT_EQ(static_cast<std::uint64_t>(1), virtual_block.track_number); + EXPECT_EQ(0x1234, virtual_block.timecode); +} + +TEST_F(VirtualBlockParserTest, IncrementalParse) { + SetReaderData({ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // Track number = 2. + 0xFF, 0xFE, // Timecode = -2. + 0x00, // Flags. + }); + + IncrementalParseAndVerify(); + + const VirtualBlock virtual_block = parser_.value(); + + EXPECT_EQ(static_cast<std::uint64_t>(2), virtual_block.track_number); + EXPECT_EQ(-2, virtual_block.timecode); +} + +} // namespace diff --git a/webm_parser/tests/void_parser_test.cc b/webm_parser/tests/void_parser_test.cc new file mode 100644 index 0000000..4223a39 --- /dev/null +++ b/webm_parser/tests/void_parser_test.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "src/void_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/element_parser_test.h" +#include "webm/element.h" +#include "webm/id.h" +#include "webm/status.h" + +using testing::NotNull; + +using webm::ElementParserTest; +using webm::Id; +using webm::kUnknownElementSize; +using webm::Status; +using webm::VoidParser; + +namespace { + +class VoidParserTest : public ElementParserTest<VoidParser, Id::kVoid> {}; + +TEST_F(VoidParserTest, InvalidSize) { + TestInit(kUnknownElementSize, Status::kInvalidElementSize); +} + +TEST_F(VoidParserTest, Empty) { + EXPECT_CALL(callback_, OnVoid(metadata_, NotNull(), NotNull())).Times(1); + + ParseAndVerify(); +} + +TEST_F(VoidParserTest, Valid) { + SetReaderData({0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}); + + EXPECT_CALL(callback_, OnVoid(metadata_, NotNull(), NotNull())).Times(1); + + ParseAndVerify(); +} + +TEST_F(VoidParserTest, IncrementalParse) { + SetReaderData({0x00, 0x01, 0x02, 0x03}); + + EXPECT_CALL(callback_, OnVoid(metadata_, NotNull(), NotNull())).Times(4); + + IncrementalParseAndVerify(); +} + +} // namespace diff --git a/webm_parser/tests/webm_parser_test.cc b/webm_parser/tests/webm_parser_test.cc new file mode 100644 index 0000000..26abbaf --- /dev/null +++ b/webm_parser/tests/webm_parser_test.cc @@ -0,0 +1,339 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "webm/webm_parser.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "test_utils/mock_callback.h" +#include "webm/buffer_reader.h" +#include "webm/status.h" + +using testing::_; +using testing::DoDefault; +using testing::InSequence; +using testing::NotNull; +using testing::Return; + +using webm::BufferReader; +using webm::Ebml; +using webm::ElementMetadata; +using webm::Id; +using webm::Info; +using webm::kUnknownElementPosition; +using webm::kUnknownElementSize; +using webm::kUnknownHeaderSize; +using webm::MockCallback; +using webm::Status; +using webm::WebmParser; + +namespace { + +class WebmParserTest : public testing::Test {}; + +TEST_F(WebmParserTest, InvalidId) { + BufferReader reader = { + 0x00, // IDs cannot start with 0x00. + }; + + MockCallback callback; + { + InSequence dummy; + + EXPECT_CALL(callback, OnEbml(_, _)).Times(0); + + EXPECT_CALL(callback, OnSegmentBegin(_, NotNull())).Times(0); + EXPECT_CALL(callback, OnSegmentEnd(_)).Times(0); + } + + WebmParser parser; + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kInvalidElementId, status.code); +} + +TEST_F(WebmParserTest, InvalidSize) { + BufferReader reader = { + 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). + 0x00, // Size must have 1+ bits set in the first byte. + }; + + MockCallback callback; + { + InSequence dummy; + + EXPECT_CALL(callback, OnEbml(_, _)).Times(0); + + EXPECT_CALL(callback, OnSegmentBegin(_, NotNull())).Times(0); + EXPECT_CALL(callback, OnSegmentEnd(_)).Times(0); + } + + WebmParser parser; + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kInvalidElementSize, status.code); +} + +TEST_F(WebmParserTest, DefaultParse) { + BufferReader reader = { + 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). + 0x80, // Size = 0. + + 0x18, 0x53, 0x80, 0x67, // ID = 0x18538067 (Segment). + 0x80, // Size = 0. + }; + + MockCallback callback; + { + InSequence dummy; + + ElementMetadata metadata = {Id::kEbml, 5, 0, 0}; + const Ebml ebml{}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1); + + metadata = {Id::kSegment, 5, 0, 5}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnSegmentBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnSegmentEnd(metadata)).Times(1); + } + + WebmParser parser; + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); +} + +TEST_F(WebmParserTest, DefaultActionIsRead) { + BufferReader reader = { + 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). + 0x80, // Size = 0. + }; + + MockCallback callback; + { + InSequence dummy; + + const ElementMetadata metadata = {Id::kEbml, 5, 0, 0}; + const Ebml ebml{}; + + // This intentionally does not set the action and relies on the parser using + // a default action value of kRead. + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())) + .WillOnce(Return(Status(Status::kOkCompleted))); + EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1); + } + + WebmParser parser; + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); +} + +TEST_F(WebmParserTest, UnknownElement) { + BufferReader reader = { + 0x80, // ID = 0x80. + 0x80, // Size = 0. + }; + + MockCallback callback; + { + InSequence dummy; + + ElementMetadata metadata = {static_cast<Id>(0x80), 2, 0, 0}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnUnknownElement(metadata, NotNull(), NotNull())) + .Times(1); + } + + WebmParser parser; + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); +} + +TEST_F(WebmParserTest, SeekEbml) { + BufferReader reader = { + 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). + 0x87, // Size = 7. + + 0x42, 0x86, // ID = 0x4286 (EBMLVersion). + 0x10, 0x00, 0x00, 0x01, // Size = 1. + 0x02, // Body (value = 2). + }; + std::uint64_t num_to_skip = 5; // Skip the starting EBML element metadata. + std::uint64_t num_actually_skipped = 0; + reader.Skip(num_to_skip, &num_actually_skipped); + EXPECT_EQ(num_to_skip, num_actually_skipped); + + MockCallback callback; + { + InSequence dummy; + + ElementMetadata metadata = {Id::kEbmlVersion, 6, 1, num_to_skip}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + + metadata = {Id::kEbml, kUnknownHeaderSize, kUnknownElementSize, + kUnknownElementPosition}; + Ebml ebml{}; + ebml.ebml_version.Set(2, true); + EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1); + } + + WebmParser parser; + parser.DidSeek(); + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(reader.size(), reader.Position()); +} + +TEST_F(WebmParserTest, SeekSegment) { + BufferReader reader = { + 0x18, 0x53, 0x80, 0x67, // ID = 0x18538067 (Segment). + 0x85, // Size = 5. + + 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info). + 0x80, // Size = 0. + }; + std::uint64_t num_to_skip = 5; // Skip the starting Segment element metadata. + std::uint64_t num_actually_skipped = 0; + reader.Skip(num_to_skip, &num_actually_skipped); + EXPECT_EQ(num_to_skip, num_actually_skipped); + + MockCallback callback; + { + InSequence dummy; + + ElementMetadata metadata = {Id::kInfo, 5, 0, num_to_skip}; + const Info info{}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnInfo(metadata, info)).Times(1); + + metadata = {Id::kSegment, kUnknownHeaderSize, kUnknownElementSize, + kUnknownElementPosition}; + EXPECT_CALL(callback, OnSegmentEnd(metadata)).Times(1); + } + + WebmParser parser; + parser.DidSeek(); + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(reader.size(), reader.Position()); +} + +TEST_F(WebmParserTest, SeekVoid) { + BufferReader reader = { + 0xEC, // ID = 0xEC (Void). + 0x81, // Size = 0. + + 0xEC, // ID = 0xEC (Void). + 0x81, // Size = 1. + 0x00, // Body. + + 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). + 0x80, // Size = 0. + }; + std::uint64_t num_to_skip = 2; // Skip the first Void element. + std::uint64_t num_actually_skipped = 0; + reader.Skip(num_to_skip, &num_actually_skipped); + EXPECT_EQ(num_to_skip, num_actually_skipped); + + MockCallback callback; + { + InSequence dummy; + + ElementMetadata metadata = {Id::kVoid, 2, 1, num_to_skip}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnVoid(metadata, &reader, NotNull())).Times(1); + + metadata = {Id::kEbml, 5, 0, 5}; + const Ebml ebml{}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1); + } + + WebmParser parser; + parser.DidSeek(); + Status status = parser.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(reader.size(), reader.Position()); +} + +TEST_F(WebmParserTest, SwapAfterFailedParse) { + BufferReader reader = { + 0x00, // Invalid ID. + + 0xEC, // ID = 0xEC (Void). + 0x81, // Size = 1. + 0x00, // Body. + }; + + MockCallback expect_nothing; + EXPECT_CALL(expect_nothing, OnElementBegin(_, _)).Times(0); + + MockCallback expect_void; + { + InSequence dummy; + + ElementMetadata metadata = {Id::kVoid, 2, 1, 1}; + EXPECT_CALL(expect_void, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(expect_void, OnVoid(metadata, &reader, NotNull())).Times(1); + } + + WebmParser parser1; + Status status = parser1.Feed(&expect_nothing, &reader); + EXPECT_EQ(Status::kInvalidElementId, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), reader.Position()); + + // After swapping, the parser should retain its failed state and not consume + // more data. + WebmParser parser2; + parser2.Swap(&parser1); + status = parser2.Feed(&expect_nothing, &reader); + EXPECT_EQ(Status::kInvalidElementId, status.code); + EXPECT_EQ(static_cast<std::uint64_t>(1), reader.Position()); + + // parser1 should be a fresh/new parser after the swap, so parsing should + // succeed. + status = parser1.Feed(&expect_void, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(reader.size(), reader.Position()); +} + +TEST_F(WebmParserTest, Swap) { + BufferReader reader = { + 0xEC, // ID = 0xEC (Void). + 0x81, // Size = 1. + 0x00, // Body. + + 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). + 0x80, // Size = 0. + }; + + MockCallback callback; + { + InSequence dummy; + + ElementMetadata metadata = {Id::kVoid, 2, 1, 0}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnVoid(metadata, &reader, NotNull())) + .WillOnce(Return(Status(Status::kOkPartial))) + .WillOnce(DoDefault()); + + metadata = {Id::kEbml, 5, 0, 3}; + EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); + EXPECT_CALL(callback, OnEbml(metadata, Ebml{})).Times(1); + } + + WebmParser parser1; + Status status = parser1.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkPartial, status.code); + + WebmParser parser2; + swap(parser1, parser2); + status = parser2.Feed(&callback, &reader); + EXPECT_EQ(Status::kOkCompleted, status.code); + EXPECT_EQ(reader.size(), reader.Position()); +} + +} // namespace diff --git a/webm_parser/tests/webm_parser_tests.cc b/webm_parser/tests/webm_parser_tests.cc new file mode 100644 index 0000000..4011572 --- /dev/null +++ b/webm_parser/tests/webm_parser_tests.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#include "gtest/gtest.h" + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}
\ No newline at end of file diff --git a/webmids.hpp b/webmids.hpp new file mode 100644 index 0000000..e0eca45 --- /dev/null +++ b/webmids.hpp @@ -0,0 +1,23 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_WEBMIDS_HPP_ +#define LIBWEBM_WEBMIDS_HPP_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "common/webmids.h" + +namespace mkvmuxer { +// MkvId moved from the mkvmuxer namespace to the libwebm namespace. Pull all +// of libwebm into mkvmuxer to ease transition to the new namespace. New +// projects should use libwebm::MkvId and should not expect to find MkvId in +// mkvmuxer. +using namespace libwebm; +} // namespace mkvmuxer + +#endif // LIBWEBM_WEBMIDS_HPP_ diff --git a/webvtt/vttreader.cc b/webvtt/vttreader.cc new file mode 100644 index 0000000..0dba9de --- /dev/null +++ b/webvtt/vttreader.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "webvtt/vttreader.h" + +namespace libwebvtt { + +VttReader::VttReader() : file_(NULL) {} + +VttReader::~VttReader() { Close(); } + +int VttReader::Open(const char* filename) { + if (filename == NULL || file_ != NULL) + return -1; + + file_ = fopen(filename, "rb"); + if (file_ == NULL) + return -1; + + return 0; // success +} + +void VttReader::Close() { + if (file_) { + fclose(file_); + file_ = NULL; + } +} + +int VttReader::GetChar(char* c) { + if (c == NULL || file_ == NULL) + return -1; + + const int result = fgetc(file_); + if (result != EOF) { + *c = static_cast<char>(result); + return 0; // success + } + + if (ferror(file_)) + return -1; // error + + if (feof(file_)) + return 1; // EOF + + return -1; // weird +} + +} // namespace libwebvtt diff --git a/webvtt/vttreader.h b/webvtt/vttreader.h new file mode 100644 index 0000000..8c3853f --- /dev/null +++ b/webvtt/vttreader.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef WEBVTT_VTTREADER_H_ +#define WEBVTT_VTTREADER_H_ + +#include <cstdio> +#include "./webvttparser.h" + +namespace libwebvtt { + +class VttReader : public libwebvtt::Reader { + public: + VttReader(); + virtual ~VttReader(); + + // Open the file identified by |filename| in read-only mode, as a + // binary stream of bytes. Returns 0 on success, negative if error. + int Open(const char* filename); + + // Closes the file stream. Note that the stream is automatically + // closed when the VttReader object is destroyed. + void Close(); + + // Reads the next character in the file stream, as per the semantics + // of Reader::GetChar. Returns negative if error, 0 on success, and + // positive if end-of-stream has been reached. + virtual int GetChar(char* c); + + private: + FILE* file_; + + VttReader(const VttReader&); + VttReader& operator=(const VttReader&); +}; + +} // namespace libwebvtt + +#endif // WEBVTT_VTTREADER_H_ diff --git a/webvtt/webvttparser.cc b/webvtt/webvttparser.cc new file mode 100644 index 0000000..c79d642 --- /dev/null +++ b/webvtt/webvttparser.cc @@ -0,0 +1,706 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#include "webvttparser.h" + +#include <ctype.h> + +#include <climits> +#include <cstddef> + +namespace libwebvtt { + +// NOLINT'ing this enum because clang-format puts it in a single line which +// makes it look really unreadable. +enum { + kNUL = '\x00', + kSPACE = ' ', + kTAB = '\x09', + kLF = '\x0A', + kCR = '\x0D' +}; // NOLINT + +Reader::~Reader() {} + +LineReader::~LineReader() {} + +int LineReader::GetLine(std::string* line_ptr) { + if (line_ptr == NULL) + return -1; + + std::string& ln = *line_ptr; + ln.clear(); + + // Consume characters from the stream, until we + // reach end-of-line (or end-of-stream). + + // The WebVTT spec states that lines may be + // terminated in any of these three ways: + // LF + // CR + // CR LF + + // We interrogate each character as we read it from the stream. + // If we detect an end-of-line character, we consume the full + // end-of-line indication, and we're done; otherwise, accumulate + // the character and repeat. + + for (;;) { + char c; + const int e = GetChar(&c); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return (ln.empty()) ? 1 : 0; + + // We have a character, so we must first determine + // whether we have reached end-of-line. + + if (c == kLF) + return 0; // handle the easy end-of-line case immediately + + if (c == kCR) + break; // handle the hard end-of-line case outside of loop + + if (c == '\xFE' || c == '\xFF') // not UTF-8 + return -1; + + // To defend against pathological or malicious streams, we + // cap the line length at some arbitrarily-large value: + enum { kMaxLineLength = 10000 }; // arbitrary + + if (ln.length() >= kMaxLineLength) + return -1; + + // We don't have an end-of-line character, so accumulate + // the character in our line buffer. + ln.push_back(c); + } + + // We detected a CR. We must interrogate the next character + // in the stream, to determine whether we have a LF (which + // would make it part of this same line). + + char c; + const int e = GetChar(&c); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return 0; + + // If next character in the stream is not a LF, return it + // to the stream (because it's part of the next line). + if (c != kLF) + UngetChar(c); + + return 0; +} + +Parser::Parser(Reader* r) : reader_(r), unget_(-1) {} + +Parser::~Parser() {} + +int Parser::Init() { + int e = ParseBOM(); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return -1; + + // Parse "WEBVTT". We read from the stream one character at-a-time, in + // order to defend against non-WebVTT streams (e.g. binary files) that don't + // happen to comprise lines of text demarcated with line terminators. + + const char kId[] = "WEBVTT"; + + for (const char* p = kId; *p; ++p) { + char c; + e = GetChar(&c); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return -1; + + if (c != *p) + return -1; + } + + std::string line; + + e = GetLine(&line); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return 0; // weird but valid + + if (!line.empty()) { + // Parse optional characters that follow "WEBVTT" + + const char c = line[0]; + + if (c != kSPACE && c != kTAB) + return -1; + } + + // The WebVTT spec requires that the "WEBVTT" line + // be followed by an empty line (to separate it from + // first cue). + + e = GetLine(&line); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return 0; // weird but we allow it + + if (!line.empty()) + return -1; + + return 0; // success +} + +int Parser::Parse(Cue* cue) { + if (cue == NULL) + return -1; + + // Parse first non-blank line + + std::string line; + int e; + + for (;;) { + e = GetLine(&line); + + if (e) // EOF is OK here + return e; + + if (!line.empty()) + break; + } + + // A WebVTT cue comprises an optional cue identifier line followed + // by a (non-optional) timings line. You determine whether you have + // a timings line by scanning for the arrow token, the lexeme of which + // may not appear in the cue identifier line. + + const char kArrow[] = "-->"; + std::string::size_type arrow_pos = line.find(kArrow); + + if (arrow_pos != std::string::npos) { + // We found a timings line, which implies that we don't have a cue + // identifier. + + cue->identifier.clear(); + } else { + // We did not find a timings line, so we assume that we have a cue + // identifier line, and then try again to find the cue timings on + // the next line. + + cue->identifier.swap(line); + + e = GetLine(&line); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return -1; + + arrow_pos = line.find(kArrow); + + if (arrow_pos == std::string::npos) // not a timings line + return -1; + } + + e = ParseTimingsLine(&line, arrow_pos, &cue->start_time, &cue->stop_time, + &cue->settings); + + if (e) // error + return e; + + // The cue payload comprises all the non-empty + // lines that follow the timings line. + + Cue::payload_t& p = cue->payload; + p.clear(); + + for (;;) { + e = GetLine(&line); + + if (e < 0) // error + return e; + + if (line.empty()) + break; + + p.push_back(line); + } + + if (p.empty()) + return -1; + + return 0; // success +} + +int Parser::GetChar(char* c) { + if (unget_ >= 0) { + *c = static_cast<char>(unget_); + unget_ = -1; + return 0; + } + + return reader_->GetChar(c); +} + +void Parser::UngetChar(char c) { unget_ = static_cast<unsigned char>(c); } + +int Parser::ParseBOM() { + // Explanation of UTF-8 BOM: + // http://en.wikipedia.org/wiki/Byte_order_mark + + static const char BOM[] = "\xEF\xBB\xBF"; // UTF-8 BOM + + for (int i = 0; i < 3; ++i) { + char c; + int e = GetChar(&c); + + if (e < 0) // error + return e; + + if (e > 0) // EOF + return 1; + + if (c != BOM[i]) { + if (i == 0) { // we don't have a BOM + UngetChar(c); + return 0; // success + } + + // We started a BOM, so we must finish the BOM. + return -1; // error + } + } + + return 0; // success +} + +int Parser::ParseTimingsLine(std::string* line_ptr, + std::string::size_type arrow_pos, Time* start_time, + Time* stop_time, Cue::settings_t* settings) { + if (line_ptr == NULL) + return -1; + + std::string& line = *line_ptr; + + if (arrow_pos == std::string::npos || arrow_pos >= line.length()) + return -1; + + // Place a NUL character at the start of the arrow token, in + // order to demarcate the start time from remainder of line. + line[arrow_pos] = kNUL; + std::string::size_type idx = 0; + + int e = ParseTime(line, &idx, start_time); + if (e) // error + return e; + + // Detect any junk that follows the start time, + // but precedes the arrow symbol. + + while (char c = line[idx]) { + if (c != kSPACE && c != kTAB) + return -1; + ++idx; + } + + // Place a NUL character at the end of the line, + // so the scanner has a place to stop, and begin + // the scan just beyond the arrow token. + + line.push_back(kNUL); + idx = arrow_pos + 3; + + e = ParseTime(line, &idx, stop_time); + if (e) // error + return e; + + e = ParseSettings(line, idx, settings); + if (e) // error + return e; + + return 0; // success +} + +int Parser::ParseTime(const std::string& line, std::string::size_type* idx_ptr, + Time* time) { + if (idx_ptr == NULL) + return -1; + + std::string::size_type& idx = *idx_ptr; + + if (idx == std::string::npos || idx >= line.length()) + return -1; + + if (time == NULL) + return -1; + + // Consume any whitespace that precedes the timestamp. + + while (char c = line[idx]) { + if (c != kSPACE && c != kTAB) + break; + ++idx; + } + + // WebVTT timestamp syntax comes in three flavors: + // SS[.sss] + // MM:SS[.sss] + // HH:MM:SS[.sss] + + // Parse a generic number value. We don't know which component + // of the time we have yet, until we do more parsing. + + int val = ParseNumber(line, &idx); + + if (val < 0) // error + return val; + + Time& t = *time; + + // The presence of a colon character indicates that we have + // an [HH:]MM:SS style syntax. + + if (line[idx] == ':') { + // We have either HH:MM:SS or MM:SS + + // The value we just parsed is either the hours or minutes. + // It must be followed by another number value (that is + // either minutes or seconds). + + const int first_val = val; + + ++idx; // consume colon + + // Parse second value + + val = ParseNumber(line, &idx); + + if (val < 0) + return val; + + if (val >= 60) // either MM or SS + return -1; + + if (line[idx] == ':') { + // We have HH:MM:SS + + t.hours = first_val; + t.minutes = val; // vetted above + + ++idx; // consume MM:SS colon + + // We have parsed the hours and minutes. + // We must now parse the seconds. + + val = ParseNumber(line, &idx); + + if (val < 0) + return val; + + if (val >= 60) // SS part of HH:MM:SS + return -1; + + t.seconds = val; + } else { + // We have MM:SS + + // The implication here is that the hour value was omitted + // from the timestamp (because it was 0). + + if (first_val >= 60) // minutes + return -1; + + t.hours = 0; + t.minutes = first_val; + t.seconds = val; // vetted above + } + } else { + // We have SS (only) + + // The time is expressed as total number of seconds, + // so the seconds value has no upper bound. + + t.seconds = val; + + // Convert SS to HH:MM:SS + + t.minutes = t.seconds / 60; + t.seconds -= t.minutes * 60; + + t.hours = t.minutes / 60; + t.minutes -= t.hours * 60; + } + + // We have parsed the hours, minutes, and seconds. + // We must now parse the milliseconds. + + char c = line[idx]; + + // TODO(matthewjheaney): one option here is to slightly relax the + // syntax rules for WebVTT timestamps, to permit the comma character + // to also be used as the seconds/milliseconds separator. This + // would handle streams that use localization conventions for + // countries in Western Europe. For now we obey the rules specified + // in the WebVTT spec (allow "full stop" only). + + const bool have_milliseconds = (c == '.'); + + if (!have_milliseconds) { + t.milliseconds = 0; + } else { + ++idx; // consume FULL STOP + + val = ParseNumber(line, &idx); + + if (val < 0) + return val; + + if (val >= 1000) + return -1; + + if (val < 10) + t.milliseconds = val * 100; + else if (val < 100) + t.milliseconds = val * 10; + else + t.milliseconds = val; + } + + // We have parsed the time proper. We must check for any + // junk that immediately follows the time specifier. + + c = line[idx]; + + if (c != kNUL && c != kSPACE && c != kTAB) + return -1; + + return 0; // success +} + +int Parser::ParseSettings(const std::string& line, std::string::size_type idx, + Cue::settings_t* settings) { + settings->clear(); + + if (idx == std::string::npos || idx >= line.length()) + return -1; + + for (;;) { + // We must parse a line comprising a sequence of 0 or more + // NAME:VALUE pairs, separated by whitespace. The line iself is + // terminated with a NUL char (indicating end-of-line). + + for (;;) { + const char c = line[idx]; + + if (c == kNUL) // end-of-line + return 0; // success + + if (c != kSPACE && c != kTAB) + break; + + ++idx; // consume whitespace + } + + // We have consumed the whitespace, and have not yet reached + // end-of-line, so there is something on the line for us to parse. + + settings->push_back(Setting()); + Setting& s = settings->back(); + + // Parse the NAME part of the settings pair. + + for (;;) { + const char c = line[idx]; + + if (c == ':') // we have reached end of NAME part + break; + + if (c == kNUL || c == kSPACE || c == kTAB) + return -1; + + s.name.push_back(c); + + ++idx; + } + + if (s.name.empty()) + return -1; + + ++idx; // consume colon + + // Parse the VALUE part of the settings pair. + + for (;;) { + const char c = line[idx]; + + if (c == kNUL || c == kSPACE || c == kTAB) + break; + + if (c == ':') // suspicious when part of VALUE + return -1; // TODO(matthewjheaney): verify this behavior + + s.value.push_back(c); + + ++idx; + } + + if (s.value.empty()) + return -1; + } +} + +int Parser::ParseNumber(const std::string& line, + std::string::size_type* idx_ptr) { + if (idx_ptr == NULL) + return -1; + + std::string::size_type& idx = *idx_ptr; + + if (idx == std::string::npos || idx >= line.length()) + return -1; + + if (!isdigit(line[idx])) + return -1; + + int result = 0; + + while (isdigit(line[idx])) { + const char c = line[idx]; + const int i = c - '0'; + + if (result > INT_MAX / 10) + return -1; + + result *= 10; + + if (result > INT_MAX - i) + return -1; + + result += i; + + ++idx; + } + + return result; +} + +bool Time::operator==(const Time& rhs) const { + if (hours != rhs.hours) + return false; + + if (minutes != rhs.minutes) + return false; + + if (seconds != rhs.seconds) + return false; + + return (milliseconds == rhs.milliseconds); +} + +bool Time::operator<(const Time& rhs) const { + if (hours < rhs.hours) + return true; + + if (hours > rhs.hours) + return false; + + if (minutes < rhs.minutes) + return true; + + if (minutes > rhs.minutes) + return false; + + if (seconds < rhs.seconds) + return true; + + if (seconds > rhs.seconds) + return false; + + return (milliseconds < rhs.milliseconds); +} + +bool Time::operator>(const Time& rhs) const { return rhs.operator<(*this); } + +bool Time::operator<=(const Time& rhs) const { return !this->operator>(rhs); } + +bool Time::operator>=(const Time& rhs) const { return !this->operator<(rhs); } + +presentation_t Time::presentation() const { + const presentation_t h = 1000LL * 3600LL * presentation_t(hours); + const presentation_t m = 1000LL * 60LL * presentation_t(minutes); + const presentation_t s = 1000LL * presentation_t(seconds); + const presentation_t result = h + m + s + milliseconds; + return result; +} + +Time& Time::presentation(presentation_t d) { + if (d < 0) { // error + hours = 0; + minutes = 0; + seconds = 0; + milliseconds = 0; + + return *this; + } + + seconds = static_cast<int>(d / 1000); + milliseconds = static_cast<int>(d - 1000 * seconds); + + minutes = seconds / 60; + seconds -= 60 * minutes; + + hours = minutes / 60; + minutes -= 60 * hours; + + return *this; +} + +Time& Time::operator+=(presentation_t rhs) { + const presentation_t d = this->presentation(); + const presentation_t dd = d + rhs; + this->presentation(dd); + return *this; +} + +Time Time::operator+(presentation_t d) const { + Time t(*this); + t += d; + return t; +} + +Time& Time::operator-=(presentation_t d) { return this->operator+=(-d); } + +presentation_t Time::operator-(const Time& t) const { + const presentation_t rhs = t.presentation(); + const presentation_t lhs = this->presentation(); + const presentation_t result = lhs - rhs; + return result; +} + +} // namespace libwebvtt diff --git a/webvtt/webvttparser.h b/webvtt/webvttparser.h new file mode 100644 index 0000000..c1f4c6b --- /dev/null +++ b/webvtt/webvttparser.h @@ -0,0 +1,158 @@ +// Copyright (c) 2012 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. + +#ifndef WEBVTT_WEBVTTPARSER_H_ +#define WEBVTT_WEBVTTPARSER_H_ + +#include <list> +#include <string> + +namespace libwebvtt { + +class Reader { + public: + // Fetch a character from the stream. Return + // negative if error, positive if end-of-stream, + // and 0 if a character is available. + virtual int GetChar(char* c) = 0; + + protected: + virtual ~Reader(); +}; + +class LineReader : protected Reader { + public: + // Consume a line of text from the stream, stripping off + // the line terminator characters. Returns negative if error, + // 0 on success, and positive at end-of-stream. + int GetLine(std::string* line); + + protected: + virtual ~LineReader(); + + // Puts a character back into the stream. + virtual void UngetChar(char c) = 0; +}; + +// As measured in thousandths of a second, +// e.g. a duration of 1 equals 0.001 seconds, +// and a duration of 1000 equals 1 second. +typedef long long presentation_t; // NOLINT + +struct Time { + int hours; + int minutes; + int seconds; + int milliseconds; + + bool operator==(const Time& rhs) const; + bool operator<(const Time& rhs) const; + bool operator>(const Time& rhs) const; + bool operator<=(const Time& rhs) const; + bool operator>=(const Time& rhs) const; + + presentation_t presentation() const; + Time& presentation(presentation_t); + + Time& operator+=(presentation_t); + Time operator+(presentation_t) const; + + Time& operator-=(presentation_t); + presentation_t operator-(const Time&) const; +}; + +struct Setting { + std::string name; + std::string value; +}; + +struct Cue { + std::string identifier; + + Time start_time; + Time stop_time; + + typedef std::list<Setting> settings_t; + settings_t settings; + + typedef std::list<std::string> payload_t; + payload_t payload; +}; + +class Parser : private LineReader { + public: + explicit Parser(Reader* r); + virtual ~Parser(); + + // Pre-parse enough of the stream to determine whether + // this is really a WEBVTT file. Returns 0 on success, + // negative if error. + int Init(); + + // Parse the next WebVTT cue from the stream. Returns 0 if + // an entire cue was parsed, negative if error, and positive + // at end-of-stream. + int Parse(Cue* cue); + + private: + // Returns the next character in the stream, using the look-back character + // if present (as per Reader::GetChar). + virtual int GetChar(char* c); + + // Puts a character back into the stream (as per LineReader::UngetChar). + virtual void UngetChar(char c); + + // Check for presence of a UTF-8 BOM in the stream. Returns + // negative if error, 0 on success, and positive at end-of-stream. + int ParseBOM(); + + // Parse the distinguished "cue timings" line, which includes the start + // and stop times and settings. Argument |line| contains the complete + // line of text (as returned by ParseLine()), which the function is free + // to modify as it sees fit, to facilitate scanning. Argument |arrow_pos| + // is the offset of the arrow token ("-->"), which indicates that this is + // the timings line. Returns negative if error, 0 on success. + // + static int ParseTimingsLine(std::string* line, + std::string::size_type arrow_pos, + Time* start_time, Time* stop_time, + Cue::settings_t* settings); + + // Parse a single time specifier (from the timings line), starting + // at the given offset; lexical scanning stops when a NUL character + // is detected. The function modifies offset |off| by the number of + // characters consumed. Returns negative if error, 0 on success. + // + static int ParseTime(const std::string& line, std::string::size_type* off, + Time* time); + + // Parse the cue settings from the timings line, starting at the + // given offset. Returns negative if error, 0 on success. + // + static int ParseSettings(const std::string& line, std::string::size_type off, + Cue::settings_t* settings); + + // Parse a non-negative integer from the characters in |line| beginning + // at offset |off|. The function increments |off| by the number + // of characters consumed. Returns the value, or negative if error. + // + static int ParseNumber(const std::string& line, std::string::size_type* off); + + Reader* const reader_; + + // Provides one character's worth of look-back, to facilitate scanning. + int unget_; + + // Disable copy ctor and copy assign for Parser. + Parser(const Parser&); + Parser& operator=(const Parser&); +}; + +} // namespace libwebvtt + +#endif // WEBVTT_WEBVTTPARSER_H_ diff --git a/webvttparser.h b/webvttparser.h new file mode 100644 index 0000000..d83e841 --- /dev/null +++ b/webvttparser.h @@ -0,0 +1,15 @@ +// Copyright (c) 2016 The WebM project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +#ifndef LIBWEBM_WEBVTTPARSER_H_ +#define LIBWEBM_WEBVTTPARSER_H_ + +// This file is a wrapper for the file included immediately after this comment. +// New projects should not include this file: include the file included below. +#include "webvtt/webvttparser.h" + +#endif // LIBWEBM_WEBVTTPARSER_H_ |